17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-27 10:37:28 +01:00

feat(vulkan): vulkan 1.4 core with the user interface and the launcher, shaders split into ogl46/vk14 with SPIR-V binaries loaded first

This commit is contained in:
2026-10-24 05:47:11 +02:00
parent 68fa5dc2da
commit bd589f272d
235 changed files with 9397 additions and 3975 deletions

View File

@@ -1,8 +1,9 @@
# .github/workflows/shaders.yml
# UNTRUSTED CONTEXT. This workflow runs fork-authored code and must not reference `secrets.*`.
#
# Checks every shader in shaders/ against GLSL 4.60 (compute stage included) with glslang,
# after the same preprocessing the game does (ci_shadervalidator). No graphics card is needed.
# Checks every shader of shaders/ogl46 against GLSL 4.60 (compute stage included) with glslang, after the same
# preprocessing the game does, and as SPIR-V for OpenGL as glslc compiles the binaries of shaders/ogl46bin; the shaders of
# shaders/vk14 as SPIR-V for Vulkan (ci_shadervalidator). No graphics card is needed.
name: Shaders
@@ -13,6 +14,7 @@ on:
paths:
- 'shaders/**'
- 'gl/**'
- 'rendering/common/**'
- 'ci_shadervalidator/**'
- '.github/workflows/shaders.yml'
pull_request:
@@ -20,6 +22,7 @@ on:
paths:
- 'shaders/**'
- 'gl/**'
- 'rendering/common/**'
- 'ci_shadervalidator/**'
- '.github/workflows/shaders.yml'
@@ -27,7 +30,7 @@ permissions: {}
jobs:
validate:
name: Validate shaders (GLSL 4.60)
name: Validate shaders (GLSL 4.60, SPIR-V)
runs-on: ubuntu-24.04
permissions:
contents: read
@@ -48,4 +51,4 @@ jobs:
- name: Validate shaders
working-directory: ci_shadervalidator
run: ./validateshaders
run: ./validateshaders

12
.gitmodules vendored
View File

@@ -20,3 +20,15 @@
[submodule "ref/json"]
path = ref/json
url = https://github.com/nlohmann/json
[submodule "ref/volk"]
path = ref/volk
url = https://github.com/zeux/volk.git
[submodule "ref/Vulkan-Headers"]
path = ref/Vulkan-Headers
url = https://github.com/KhronosGroup/Vulkan-Headers.git
[submodule "ref/VulkanMemoryAllocator"]
path = ref/VulkanMemoryAllocator
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
[submodule "ref/glslang"]
path = ref/glslang
url = https://github.com/KhronosGroup/glslang.git

View File

@@ -49,6 +49,7 @@ file(GLOB HEADERS "*.h"
"simulation/*.h"
"scene/*.h"
"rendering/*.h"
"rendering/common/*.h"
"rendering/opengl46/*.h"
"rendering/vulkan14/*.h"
"audio/*.h"
@@ -307,6 +308,7 @@ set(SOURCES
"gl/postfx.cpp"
"gl/cubemap.cpp"
"gl/glsl_common.cpp"
"rendering/common/shadersource.cpp"
"gl/buffer.cpp"
"gl/fence.cpp"
"gl/pbo.cpp"
@@ -411,8 +413,11 @@ if (WITH_VULKAN)
# the vulkan loader is loaded by volk when the program runs, so the program starts without it (and runs with opengl)
set(VULKAN_HEADERS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ref/Vulkan-Headers/include")
set(VOLK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ref/volk")
if (NOT EXISTS "${VOLK_DIR}/volk.h" OR NOT EXISTS "${VULKAN_HEADERS_DIR}/vulkan/vulkan_core.h")
message(FATAL_ERROR "WITH_VULKAN=ON needs the submodules ref/volk and ref/Vulkan-Headers: git submodule update --init ref/volk ref/Vulkan-Headers")
# the memory allocator (header only) and the shader compiler of the renderer
set(VMA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ref/VulkanMemoryAllocator/include")
set(GLSLANG_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ref/glslang")
if (NOT EXISTS "${VOLK_DIR}/volk.h" OR NOT EXISTS "${VULKAN_HEADERS_DIR}/vulkan/vulkan_core.h" OR NOT EXISTS "${VMA_DIR}/vk_mem_alloc.h" OR NOT EXISTS "${GLSLANG_DIR}/CMakeLists.txt")
message(FATAL_ERROR "WITH_VULKAN=ON needs the submodules ref/volk, ref/Vulkan-Headers, ref/VulkanMemoryAllocator and ref/glslang: git submodule update --init ref/volk ref/Vulkan-Headers ref/VulkanMemoryAllocator ref/glslang")
endif()
set(SOURCES ${SOURCES}
"rendering/vulkan14/vulkan14common.cpp"
@@ -420,9 +425,26 @@ if (WITH_VULKAN)
"rendering/vulkan14/vulkan14instance.cpp"
"rendering/vulkan14/vulkan14device.cpp"
"rendering/vulkan14/vulkan14swapchain.cpp"
"rendering/vulkan14/vulkan14memory.cpp"
"rendering/vulkan14/vulkan14vma.cpp"
"rendering/vulkan14/vulkan14shaders.cpp"
"rendering/vulkan14/vulkan14descriptors.cpp"
"rendering/vulkan14/vulkan14passes.cpp"
"rendering/vulkan14/vulkan14texture.cpp"
"rendering/vulkan14/vulkan14imgui.cpp"
"rendering/vulkan14/vulkan14renderer.cpp"
"ref/volk/volk.c")
set(DEFINITIONS ${DEFINITIONS} "WITH_VULKAN")
# glslang compiles the shaders of shaders/vk14 when the program runs (no spirv-tools, no hlsl, no programs of its own)
set(GLSLANG_TESTS OFF CACHE BOOL "" FORCE)
set(GLSLANG_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "" FORCE)
set(ENABLE_OPT OFF CACHE BOOL "" FORCE)
set(ENABLE_HLSL OFF CACHE BOOL "" FORCE)
set(ENABLE_SPVREMAPPER OFF CACHE BOOL "" FORCE)
set(BUILD_EXTERNAL OFF CACHE BOOL "" FORCE)
add_subdirectory("${GLSLANG_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/glslang" EXCLUDE_FROM_ALL)
endif()
add_subdirectory("${DEPS_DIR}/glfw/" EXCLUDE_FROM_ALL)
@@ -552,8 +574,8 @@ add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
target_link_libraries(${PROJECT_NAME} eu07_core)
if (WITH_VULKAN)
# the vulkan headers are seen by the files of rendering/vulkan14 only, they include them
target_include_directories(${PROJECT_NAME} PRIVATE "${VULKAN_HEADERS_DIR}" "${VOLK_DIR}")
target_link_libraries(${PROJECT_NAME} ${CMAKE_DL_LIBS})
target_include_directories(${PROJECT_NAME} PRIVATE "${VULKAN_HEADERS_DIR}" "${VOLK_DIR}" "${VMA_DIR}")
target_link_libraries(${PROJECT_NAME} ${CMAKE_DL_LIBS} glslang glslang-default-resource-limits)
endif()
target_compile_definitions(eu07_settings INTERFACE ${DEFINITIONS})

View File

@@ -145,16 +145,13 @@ void eu07_application::queue_screenshot()
int eu07_application::run_crashgui()
{
bool autoup = false;
bool y{false}, n{false};
while (!glfwWindowShouldClose(m_windows.front()))
{
glfwPollEvents();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// the questions are the ui of the frames the renderer draws (render_ui()), whichever api it is: the window has no opengl
// context with vulkan
m_crashui = [&]() {
ui_layer::begin_ui_frame_internal();
bool y, n;
if (Global.asLang == "pl")
{
ImGui::Begin("Raportowanie błędów", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoResize);
@@ -201,22 +198,35 @@ int eu07_application::run_crashgui()
}
ui_layer::render_internal();
glfwSwapBuffers(m_windows.front());
};
while (!glfwWindowShouldClose(m_windows.front()))
{
glfwPollEvents();
y = false;
n = false;
if (!GfxRenderer->Render())
break;
GfxRenderer->SwapBuffers();
if (y)
{
crashreport_upload_accept();
if (autoup)
crashreport_set_autoupload();
m_crashui = nullptr;
return 0;
}
if (n)
{
crashreport_upload_reject();
m_crashui = nullptr;
return 0;
}
}
m_crashui = nullptr;
return -1;
}
#if WITH_DISCORD_RPC
@@ -382,15 +392,6 @@ int eu07_application::init(int Argc, char *Argv[])
return result;
}
if (crashreport_is_pending())
{ // run crashgui as early as possible
if ((result = run_crashgui()) != 0)
{
ErrorLog("Failed to run crash gui!");
return result;
}
}
if ((result = init_locale()) != 0)
{
ErrorLog("Failed to initialize locales! Maybe you're missing lang directory?");
@@ -408,6 +409,14 @@ int eu07_application::init(int Argc, char *Argv[])
ErrorLog("Failed to init UI!");
return result;
}
if (crashreport_is_pending())
{ // run crashgui as early as possible: it's drawn by the renderer
if ((result = run_crashgui()) != 0)
{
ErrorLog("Failed to run crash gui!");
return result;
}
}
if ((result = init_audio()) != 0)
{
ErrorLog("Failed to initialize OpenAL");
@@ -632,7 +641,6 @@ int eu07_application::run()
Timer::subsystem.loop_tasks.start();
m_taskqueue.update();
Timer::subsystem.loop_tasks.stop();
opengl_texture::reset_unit_cache();
if (!GfxRenderer->Render())
break;
@@ -747,6 +755,11 @@ void eu07_application::exit()
void eu07_application::render_ui()
{
if (m_crashui)
{
m_crashui();
return;
}
if (m_modestack.empty())
{

View File

@@ -123,6 +123,8 @@ private:
// members
bool m_screenshot_queued = false;
// the ui of the crash gui, drawn in the frames of the renderer instead of the ui of the modes while it's there
std::function<void()> m_crashui;
modeptr_array m_modes { nullptr }; // collection of available application behaviour modes
mode_stack m_modestack; // current behaviour mode

View File

@@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/.
#include "application/scenarioloaderuilayer.h"
#include "utilities/Globals.h"
#include "rendering/renderer.h"
#include "utilities/translation.h"
#include <nlohmann/json.hpp>
#include "utilities/Logs.h"
@@ -309,18 +310,13 @@ void scenarioloader_ui::generate_gradient_tex()
image_data[(y * image_width + x) * 4 + 3] = std::clamp(static_cast<int>(pow(y / 1023.f, 0.7) * 255), 0, 255);
}
// Create a OpenGL texture identifier
GLuint image_texture;
glGenTextures(1, &image_texture);
glBindTexture(GL_TEXTURE_2D, image_texture);
// Setup filtering parameters for display
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Upload pixels into texture
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_width, image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data);
// an image of the user interface: the values as they are (not srgb), the nearest texel
imgui_renderer::image_options options;
options.srgb = false;
options.nearest = true;
std::uint64_t image_texture{0};
if (auto *renderer{GfxRenderer->GetImguiRenderer()})
image_texture = renderer->Create_Image(reinterpret_cast<std::uint8_t const *>(image_data), image_width, image_height, options);
delete[] image_data;

View File

@@ -15,7 +15,7 @@ http://mozilla.org/MPL/2.0/.
class scenarioloader_ui : public ui_layer {
int m_gradient_overlay_width;
int m_gradient_overlay_height;
GLuint m_gradient_overlay_tex;
std::uint64_t m_gradient_overlay_tex; // imgui_renderer image
deferred_image m_loading_wheel_frames[60];
std::vector<std::string> m_trivia;
public:

View File

@@ -421,7 +421,11 @@ void ui_layer::shutdown()
{
ImGui::EndFrame();
GfxRenderer->GetImguiRenderer()->Shutdown();
// the vulkan renderer is gone by now (its backend of imgui went with it)
if (auto *renderer{GfxRenderer->GetImguiRenderer()})
{
renderer->Shutdown();
}
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
}

View File

@@ -1,16 +1,20 @@
#!/bin/sh
# Offline GLSL validator for shaders/ (the same preprocessing as gl::shader in the game, then glslangValidator).
# Offline validator of the shaders (shaders/ogl46 and shaders/vk14): the same preprocessing as the game
# (rendering/common/shadersource.cpp), then glslangValidator.
#
# Requirements: g++ with C++20, glslangValidator in PATH (Debian/Ubuntu: apt install glslang-tools),
# submodule ref/glm (git submodule update --init ref/glm).
# Usage (Linux; build.sh works from any directory, validateshaders reads ../shaders/ so it runs from here):
# ci_shadervalidator/build.sh
# cd ci_shadervalidator && ./validateshaders
# Every .vert/.frag/.geom/.comp under shaders/ is checked twice (features on and off).
# Every .vert/.frag/.geom/.comp of shaders/ogl46 is compiled as the game compiles the source (features on and off, and the
# variant of the multi draws) and to SPIR-V for opengl from the file as it is (as glslc makes the binaries of
# shaders/ogl46bin); the constants written in shaders/ogl46/common.glsl are compared with those of the game. The shaders of
# shaders/vk14 are compiled to SPIR-V for vulkan.
# Exit code is 0 when all shaders pass, 1 otherwise; failing shaders are listed at the end.
#
# The validator's own stdafx.h and utilities/{Globals,Logs}.h stand in for the game's headers,
# so the repository root must not be on the include path.
# The validator's own stdafx.h and utilities/{Globals,Logs}.h stand in for the game's headers: its directory comes first
# on the include path, before the repository root.
set -e
cd "$(dirname "$0")"
g++ -std=c++20 -O1 -DSHADERVALIDATOR_STANDALONE ../ref/glad/src/glad.c ../gl/glsl_common.cpp ../gl/shader.cpp main.cpp -I. -I../ref/glad/include -I../ref/glm -o validateshaders
g++ -std=c++20 -O1 ../ref/glad/src/glad.c ../gl/glsl_common.cpp ../rendering/common/shadersource.cpp main.cpp -I. -I.. -I../ref/glad/include -I../ref/glm -o validateshaders

View File

@@ -1,8 +1,9 @@
#include "stdafx.h"
#include "../gl/shader.h"
#include "../gl/glsl_common.h"
#include "rendering/common/shadersource.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include <algorithm>
#include <iostream>
#include <fstream>
#include <cstdlib>
@@ -11,99 +12,123 @@
_global Global;
bool check_shader(std::string filename, std::string const &defines = {})
namespace
{
gl::shader shader_processor;
shader_processor.name = filename;
gl::glsl_common_setup();
std::pair<GLuint, std::string> source = shader_processor.process_source(filename, "../shaders/", defines);
std::string const ogl46_dir{"../shaders/ogl46/"};
std::string const vk14_dir{"../shaders/vk14/"};
// the vulkan environment glslang is asked for: 1.4 where it knows it
std::string vulkan_env{"vulkan1.4"};
std::string stage;
if (source.first == GL_VERTEX_SHADER)
stage = "vert";
else if (source.first == GL_FRAGMENT_SHADER)
stage = "frag";
else if (source.first == GL_GEOMETRY_SHADER)
stage = "geom";
else if (source.first == GL_COMPUTE_SHADER)
stage = "comp";
else
return false;
char const *stage_name(gfx::shader_stage const Stage)
{
switch (Stage)
{
case gfx::shader_stage::vertex:
return "vert";
case gfx::shader_stage::fragment:
return "frag";
case gfx::shader_stage::geometry:
return "geom";
case gfx::shader_stage::compute:
return "comp";
}
return "vert";
}
// Mode: the options of glslangValidator (empty: glsl of opengl, compiled from the source as the game does)
bool check_text(gfx::shader_source const &Source, std::string const &Mode)
{
if (ErrorLogged)
{
ErrorLogged = false;
return false;
}
std::ofstream file("/tmp/shader");
file << source.second;
file << Source.text;
file.close();
int ret = system(("glslangValidator -S " + stage + " /tmp/shader").c_str());
// the output only when it fails (glslangValidator names the file it compiled to spir-v)
auto const command{"glslangValidator " + Mode + " -S " + stage_name(Source.stage) + " /tmp/shader" + (Mode.empty() ? "" : " -o /tmp/shader.spv") + " > /tmp/shader.log 2>&1"};
int ret = system(command.c_str());
if (ret != 0)
{
std::ifstream log("/tmp/shader.log");
std::cerr << std::endl << log.rdbuf();
std::cerr << "(source strings:";
for (std::size_t i = 0; i < Source.files.size(); ++i)
std::cerr << " " << i << ": " << Source.files[i];
std::cerr << ")" << std::endl;
}
return ret == 0;
}
bool check_shader_allopts(std::string filename)
void set_features(bool const Enabled)
{
Global.gfx_shadowmap_enabled = Enabled;
Global.gfx_envmap_enabled = Enabled;
Global.gfx_postfx_motionblur_enabled = Enabled;
Global.gfx_skippipeline = !Enabled;
Global.gfx_extraeffects = Enabled;
Global.gfx_shadergamma = Enabled;
}
// a shader of the opengl renderer: compiled from the source as the game does it (with the features on and off, and the variant
// of the multi draws), and compiled to SPIR-V for opengl from the file as it is, as glslc makes the binaries of shaders/ogl46bin
bool check_ogl46(std::string const &Name)
{
bool suc = true;
auto const stage{gfx::shader_stage_of(Name)};
auto const source = [&](std::string const &Defines, std::string const &Preamble) { return gfx::preprocess_shader(gfx::shader_api::opengl46, Name, Defines, Preamble, false, ogl46_dir); };
std::cout << "features on..." << std::flush;
Global.gfx_shadowmap_enabled = true;
Global.gfx_envmap_enabled = true;
Global.gfx_postfx_motionblur_enabled = true;
Global.gfx_skippipeline = false;
Global.gfx_extraeffects = true;
Global.gfx_shadergamma = true;
if (!check_shader(filename))
set_features(true);
if (!check_text(source({}, gl::glsl_common + gl::glsl_settings_defines()), {}))
suc = false;
std::cout << "done, features off..." << std::flush;
Global.gfx_shadowmap_enabled = false;
Global.gfx_envmap_enabled = false;
Global.gfx_postfx_motionblur_enabled = false;
Global.gfx_skippipeline = true;
Global.gfx_extraeffects = false;
Global.gfx_shadergamma = false;
if (!check_shader(filename))
set_features(false);
if (!check_text(source({}, gl::glsl_common + gl::glsl_settings_defines()), {}))
suc = false;
// the variant of the vertex and fragment stages the multi draws of the instances use (model blocks by gl_DrawID)
auto const ext{std::filesystem::path(filename).extension()};
if (ext == ".vert" || ext == ".frag")
bool const variants{(stage == gfx::shader_stage::vertex) || (stage == gfx::shader_stage::fragment)};
if (variants)
{
std::cout << "done, model draws..." << std::flush;
Global.gfx_shadowmap_enabled = true;
Global.gfx_envmap_enabled = true;
Global.gfx_postfx_motionblur_enabled = true;
Global.gfx_skippipeline = false;
Global.gfx_extraeffects = true;
Global.gfx_shadergamma = true;
if (!check_shader(filename, "#define MODEL_DRAWS 1\n"))
set_features(true);
if (!check_text(source("#define MODEL_DRAWS 1\n", gl::glsl_common + gl::glsl_settings_defines()), {}))
suc = false;
}
std::cout << "done" << std::flush;
return suc;
}
bool check_shader_confs(std::string filename)
{
bool suc = true;
std::cout << "desktop GL: " << std::flush;
if (!check_shader_allopts(filename))
std::cout << "done, spir-v..." << std::flush;
if (!check_text(source({}, {}), "-G"))
suc = false;
if (variants && !check_text(source("#define MODEL_DRAWS 1\n", {}), "-G"))
suc = false;
std::cout << std::endl;
std::cout << "done" << std::flush;
return suc;
}
// a shader of the vulkan renderer, compiled to SPIR-V for vulkan as the game does it (and as glslc makes the binaries of
// shaders/vk14bin)
bool check_vk14(std::string const &Name)
{
std::cout << "spir-v..." << std::flush;
auto const suc{check_text(gfx::preprocess_shader(gfx::shader_api::vulkan14, Name, {}, {}, false, vk14_dir), "-V --target-env " + vulkan_env)};
std::cout << "done" << std::flush;
return suc;
}
bool is_stage(std::filesystem::path const &Path)
{
auto const ext = Path.extension();
return (ext == ".frag" || ext == ".geom" || ext == ".vert" || ext == ".comp");
}
} // namespace
int main()
{
if (system("glslangValidator --version > /dev/null 2>&1") != 0)
@@ -111,23 +136,64 @@ int main()
std::cerr << "glslangValidator not found in PATH" << std::endl;
return 1;
}
{
std::ofstream file("/tmp/shader_env.vert");
file << "#version 460\nvoid main() { gl_Position = vec4(0.0); }\n";
}
if (system("glslangValidator -V --target-env vulkan1.4 /tmp/shader_env.vert -o /tmp/shader_env.spv > /dev/null 2>&1") != 0)
{
vulkan_env = "vulkan1.3";
std::cout << "== glslangValidator doesn't know vulkan 1.4, the vulkan shaders are checked against vulkan 1.3" << std::endl;
}
bool suc = true;
std::vector<std::string> failed;
for (auto &f : std::filesystem::recursive_directory_iterator("../shaders/")) {
if (!f.is_regular_file())
continue;
std::string relative = std::filesystem::relative(f.path(), "../shaders/").string();
auto const ext = f.path().extension();
if (ext != ".frag" && ext != ".geom" && ext != ".vert" && ext != ".comp")
continue;
// the binaries are compiled with the constants written in common.glsl, the game compiles the sources with its own
gl::glsl_common_setup();
std::vector<std::string> differences;
std::cout << "== constants of shaders/ogl46/common.glsl: " << std::flush;
if (gl::glsl_common_matches(ogl46_dir, differences))
{
std::cout << "the same as in the game" << std::endl;
}
else
{
std::cout << "not the same as in the game:" << std::endl;
for (auto const &difference : differences)
std::cout << " " << difference << std::endl;
suc = false;
failed.push_back("common.glsl");
}
std::cout << "== validating shader " << relative << ": " << std::flush;
if (!check_shader_confs(relative))
for (auto const &[directory, api] : {std::pair{ogl46_dir, gfx::shader_api::opengl46}, std::pair{vk14_dir, gfx::shader_api::vulkan14}})
{
if (false == std::filesystem::is_directory(directory))
continue;
std::vector<std::string> names;
for (auto &f : std::filesystem::directory_iterator(directory))
if (f.is_regular_file() && is_stage(f.path()))
names.push_back(f.path().filename().string());
std::sort(names.begin(), names.end());
for (auto const &name : names)
{
suc = false;
failed.push_back(relative);
auto const label{std::string(api == gfx::shader_api::opengl46 ? "ogl46/" : "vk14/") + name};
std::cout << "== validating shader " << label << ": " << std::flush;
bool ok{false};
try
{
ok = (api == gfx::shader_api::opengl46 ? check_ogl46(name) : check_vk14(name));
}
catch (std::exception const &e)
{
std::cerr << e.what() << std::endl;
}
std::cout << std::endl;
if (!ok)
{
suc = false;
failed.push_back(label);
}
}
}

View File

@@ -1,19 +1,24 @@
#include "stdafx.h"
#include "glsl_common.h"
#include "rendering/common/shadersource.h"
std::string gl::glsl_common;
void gl::glsl_common_setup()
{
// the values shaders/ogl46/common.glsl takes instead of its own (MASZYNA_CONSTANTS). the settings of the game aren't here,
// they are specialization constants of the binaries (glsl_settings_defines(), glsl_settings_values())
glsl_common =
"#define SHADOWMAP_ENABLED " + std::to_string((int)Global.gfx_shadowmap_enabled) + "\n" +
"#define ENVMAP_ENABLED " + std::to_string((int)Global.gfx_envmap_enabled) + "\n" +
"#define MOTIONBLUR_ENABLED " + std::to_string((int)Global.gfx_postfx_motionblur_enabled) + "\n" +
"#define POSTFX_ENABLED " + std::to_string((int)!Global.gfx_skippipeline) + "\n" +
"#define EXTRAEFFECTS_ENABLED " + std::to_string((int)Global.gfx_extraeffects) + "\n" +
"#define MASZYNA_CONSTANTS 1\n"
"#define MAX_LIGHTS " + std::to_string(MAX_LIGHTS) + "U\n" +
"#define MAX_CASCADES " + std::to_string(MAX_CASCADES) + "U\n" +
"#define MAX_PARAMS " + std::to_string(MAX_PARAMS) + "U\n" +
"#define SHADOW_TEX " + std::to_string(SHADOW_TEX) + "\n" +
"#define ENV_TEX " + std::to_string(ENV_TEX) + "\n" +
"#define HEADLIGHT_TEX " + std::to_string(HEADLIGHT_TEX) + "\n" +
"#define ENV_GGX_TEX " + std::to_string(ENV_GGX_TEX) + "\n" +
"#define SCENE_UBO_BINDING " + std::to_string(SCENE_UBO_BINDING) + "\n" +
"#define MODEL_UBO_BINDING " + std::to_string(MODEL_UBO_BINDING) + "\n" +
"#define INSTANCE_SSBO_BINDING " + std::to_string(INSTANCE_SSBO_BINDING) + "\n" +
"#define INSTANCE_RECORD_SSBO_BINDING " + std::to_string(INSTANCE_RECORD_SSBO_BINDING) + "\n" +
"#define INSTANCE_JOB_SSBO_BINDING " + std::to_string(INSTANCE_JOB_SSBO_BINDING) + "\n" +
@@ -86,223 +91,34 @@ void gl::glsl_common_setup()
"#define MAX_CLOUD_LAYERS " + std::to_string(MAX_CLOUD_LAYERS) + "\n" +
"#define FOG_HORIZON_SAMPLES " + std::to_string(FOG_HORIZON_SAMPLES) + "\n" +
"#define WEATHER_GROUP_SIZE " + std::to_string(WEATHER_GROUP_SIZE) + "\n" +
"#define FOG_VOLUME_BIAS " + std::to_string(FOG_VOLUME_BIAS) + "\n" +
R"STRING(
const uint LIGHT_SPOT = 0U;
const uint LIGHT_POINT = 1U;
const uint LIGHT_DIR = 2U;
const uint LIGHT_HEADLIGHTS = 3U;
struct light_s
{
vec3 pos;
uint type;
vec3 dir;
float in_cutoff;
vec3 color;
float out_cutoff;
float linear;
float quadratic;
float intensity;
float ambient;
mat4 headlight_projection;
vec4 headlight_weights;
};
// storage blocks are guaranteed only in fragment and compute shaders (GL 4.6 minimum for the other stages is 0)
#if defined(STAGE_FRAGMENT) || defined(STAGE_COMPUTE)
layout(std430, binding = LIGHT_SSBO_BINDING) readonly buffer light_ssbo
{
vec3 ambient;
// lights stored in lights[], what the cluster culling goes through
uint lights_total;
vec3 fog_color;
// lights the shading loop goes through when the lights are not clustered
uint lights_count;
// 1 if the shading takes the lights from the list of the cluster of a fragment, see cluster_ubo
uint lights_clustered;
// scale of the directional part of the sky ambient light, see sky_ssbo
float sky_ambient_factor;
// 1 if rough reflections come from the prefiltered environment map
uint env_ggx;
light_s lights[];
};
// directional part of the sky ambient light: real spherical harmonics (bands 1 and 2, band 0 is ambient) of the light
// a surface facing a world space direction gets, in units of ambient. see gl::sky_data
layout(std430, binding = SKY_SSBO_BINDING) readonly buffer sky_ssbo
{
vec4 sky_ambient_sh[9];
};
#endif
// light clusters, see gl/cluster_grid.h
struct cluster_aabb_s
{
vec4 lower;
vec4 upper;
};
struct cluster_range_s
{
uint offset;
uint count;
};
#if defined(STAGE_FRAGMENT) || defined(STAGE_COMPUTE)
// parameters of the grid for the pass being drawn
layout(std140, binding = CLUSTER_UBO_BINDING) uniform cluster_ubo
{
mat4 cluster_projection;
float cluster_znear;
float cluster_zfar;
float cluster_slice_scale;
float cluster_slice_bias;
vec2 cluster_viewport_size;
uint cluster_valid;
};
// the compute passes fill the cluster buffers, shading only reads them
#ifdef STAGE_COMPUTE
#define CLUSTER_SSBO_ACCESS
layout(std430, binding = CLUSTER_AABB_SSBO_BINDING) buffer cluster_aabb_ssbo
{
cluster_aabb_s cluster_aabbs[];
};
layout(std430, binding = CLUSTER_COUNTER_SSBO_BINDING) buffer cluster_counter_ssbo
{
uint cluster_index_count;
uint cluster_overflow_count;
};
#else
#define CLUSTER_SSBO_ACCESS readonly
#endif
layout(std430, binding = CLUSTER_RANGE_SSBO_BINDING) CLUSTER_SSBO_ACCESS buffer cluster_range_ssbo
{
cluster_range_s cluster_ranges[];
};
layout(std430, binding = CLUSTER_INDEX_SSBO_BINDING) CLUSTER_SSBO_ACCESS buffer cluster_index_ssbo
{
uint cluster_light_indices[];
};
#endif
#ifdef MODEL_DRAWS
// programs of the multi draws of the instances drawn from the gpu culling: the model block of every draw comes from a
// storage buffer, picked by the number of the draw in the multi draw. the fragment stage gets the number from the vertex
// stage (which writes f_model_draw), and the names of the uniform block below stand for the fields of the draw
struct model_draw_s
{
mat4 modelview;
mat3 modelviewnormal;
vec4 param[MAX_PARAMS];
mat4 future;
float opacity;
float emission;
float fog_density;
float alpha_mult;
float shadow_tone;
};
layout(std430, binding = MODEL_DRAW_SSBO_BINDING) readonly buffer model_draw_ssbo
{
model_draw_s model_draws[];
};
#if defined(STAGE_VERTEX)
flat out uint f_model_draw;
#define MODEL_DRAW gl_DrawID
#elif defined(STAGE_FRAGMENT)
flat in uint f_model_draw;
#define MODEL_DRAW f_model_draw
#else
#define MODEL_DRAW 0
#endif
#define modelview model_draws[MODEL_DRAW].modelview
#define modelviewnormal model_draws[MODEL_DRAW].modelviewnormal
#define param model_draws[MODEL_DRAW].param
#define future model_draws[MODEL_DRAW].future
#define opacity model_draws[MODEL_DRAW].opacity
#define emission model_draws[MODEL_DRAW].emission
#define fog_density model_draws[MODEL_DRAW].fog_density
#define alpha_mult model_draws[MODEL_DRAW].alpha_mult
#define shadow_tone model_draws[MODEL_DRAW].shadow_tone
#else
layout (std140) uniform model_ubo
{
mat4 modelview;
mat3 modelviewnormal;
vec4 param[MAX_PARAMS];
mat4 future;
float opacity;
float emission;
float fog_density;
float alpha_mult;
float shadow_tone;
};
#endif
layout (std140) uniform scene_ubo
{
mat4 projection;
mat4 inv_view;
mat4 lightview[MAX_CASCADES];
vec3 cascade_end;
float time;
vec4 rain_params;
vec4 wiper_pos;
vec4 wiper_timer_out;
vec4 wiper_timer_return;
// shadow maps of the headlights, see gl::scene_ubs
mat4 headlight_shadow[MAX_HEADLIGHT_SHADOWS];
vec4 headlight_shadow_params[MAX_HEADLIGHT_SHADOWS];
// contact shadows, see gl::scene_ubs
vec4 contact_shadow_params;
vec4 contact_shadow_lamps;
// cascades of the sun shadows fitted to the depth of the view, see gl::scene_ubs
vec4 shadow_fit;
};
#ifdef STAGE_VERTEX
// Per-instance modelview matrices for GPU-instanced draws, element 0 is identity.
// Draws which aren't instanced have gl_BaseInstance == 0 and gl_InstanceID == 0,
// so effective_modelview() reduces to model_ubo.modelview. For instanced draws
// the C++ side uploads camera-space root transforms of the instances and draws
// them from base instance onwards, with model_ubo.modelview set to the submodel-local
// chain (computed from identity, NOT from the camera/instance), so the product
// gives the correct final transform per instance per submodel.
layout(std430, binding = INSTANCE_SSBO_BINDING) readonly buffer instance_ssbo
{
mat4 instance_modelview[];
};
mat4 effective_modelview() {
return instance_modelview[gl_BaseInstance + gl_InstanceID] * modelview;
}
mat3 effective_modelviewnormal() {
// instance_modelview can include per-instance scale (uniform or per-axis),
// so its upper-3x3 is NOT its own inverse-transpose in the non-uniform
// case. Compute the correct normal matrix via transpose(inverse(...)).
// For pure rotation+translation this still produces the rotation matrix
// (one extra mat3 inverse per vertex — modern GPUs handle it cheaply).
mat3 instance_mv3 = mat3(instance_modelview[gl_BaseInstance + gl_InstanceID]);
mat3 instance_normal = transpose(inverse(instance_mv3));
return instance_normal * modelviewnormal;
}
#endif
)STRING";
"#define FOG_VOLUME_BIAS " + std::to_string(FOG_VOLUME_BIAS) + "\n";
}
std::string gl::glsl_settings_defines()
{
auto const value = [](bool const Value) { return std::string(Value ? "true" : "false"); };
return
"#define SHADOWMAP_ENABLED_VALUE " + value(Global.gfx_shadowmap_enabled) + "\n"
"#define ENVMAP_ENABLED_VALUE " + value(Global.gfx_envmap_enabled) + "\n"
"#define MOTIONBLUR_ENABLED_VALUE " + value(Global.gfx_postfx_motionblur_enabled) + "\n"
"#define POSTFX_ENABLED_VALUE " + value(!Global.gfx_skippipeline) + "\n"
"#define EXTRAEFFECTS_ENABLED_VALUE " + value(Global.gfx_extraeffects) + "\n"
"#define OUTPUT_GAMMA_VALUE " + value(Global.gfx_shadergamma) + "\n";
}
std::array<GLuint, 6> gl::glsl_settings_values()
{
// in the order of gl::shader_setting
return {
static_cast<GLuint>(Global.gfx_shadowmap_enabled),
static_cast<GLuint>(Global.gfx_envmap_enabled),
static_cast<GLuint>(Global.gfx_postfx_motionblur_enabled),
static_cast<GLuint>(!Global.gfx_skippipeline),
static_cast<GLuint>(Global.gfx_extraeffects),
static_cast<GLuint>(Global.gfx_shadergamma) };
}
bool gl::glsl_common_matches(std::string const &Basedir, std::vector<std::string> &Differences)
{
return gfx::shader_constants_match(gfx::shader_api::opengl46, "common.glsl", glsl_common, Basedir, Differences);
}

View File

@@ -1,10 +1,22 @@
#pragma once
#include <array>
#include <string>
#include <vector>
#include "ubo.h"
#include "utilities/Globals.h"
namespace gl
{
// the constants of the game the shaders take (shaders/ogl46/common.glsl), as #define lines put before the sources
extern std::string glsl_common;
void glsl_common_setup();
// the settings of the game the shaders take (shaders/ogl46/settings.glsl): #define lines of their values for the sources,
// and the values of the specialization constants of the binaries, in the order of gl::shader_setting
std::string glsl_settings_defines();
std::array<GLuint, 6> glsl_settings_values();
// whether the constants written in common.glsl in Basedir (shaders/ogl46/ when empty) are those of the game: the SPIR-V
// binaries are compiled with the ones of the file. Differences: what isn't the same
bool glsl_common_matches(std::string const &Basedir, std::vector<std::string> &Differences);
}

View File

@@ -1,292 +1,232 @@
#include "stdafx.h"
#include <fstream>
#include <sstream>
#include "shader.h"
#include "glsl_common.h"
#include "utilities/Logs.h"
inline bool strcend(std::string_view value, std::string_view ending)
#include <cstring>
// the values of the components are those of opengl (material textures take them as the hint of their format)
static_assert(static_cast<GLint>(gfx::shader_components::R) == GL_RED);
static_assert(static_cast<GLint>(gfx::shader_components::RG) == GL_RG);
static_assert(static_cast<GLint>(gfx::shader_components::RGB) == GL_RGB);
static_assert(static_cast<GLint>(gfx::shader_components::RGBA) == GL_RGBA);
static_assert(static_cast<GLint>(gfx::shader_components::sRGB) == GL_SRGB);
static_assert(static_cast<GLint>(gfx::shader_components::sRGB_A) == GL_SRGB_ALPHA);
static_assert(gfx::MAX_SHADER_TEXTURES == gl::MAX_TEXTURES);
static_assert(gfx::MAX_SHADER_PARAMS == gl::MAX_PARAMS);
namespace
{
if (ending.size() > value.size())
GLenum shader_type(gfx::shader_stage const Stage)
{
switch (Stage)
{
case gfx::shader_stage::vertex:
return GL_VERTEX_SHADER;
case gfx::shader_stage::fragment:
return GL_FRAGMENT_SHADER;
case gfx::shader_stage::geometry:
return GL_GEOMETRY_SHADER;
case gfx::shader_stage::compute:
return GL_COMPUTE_SHADER;
}
return GL_VERTEX_SHADER;
}
gfx::shader_source preprocess(std::string const &Name, std::string const &Defines, bool const Allowmissing)
{
return gfx::preprocess_shader(gfx::shader_api::opengl46, Name, Defines, gl::glsl_common + gl::glsl_settings_defines(), Allowmissing);
}
// the files of the sources named by their numbers, for the messages of the compiler (#line <line> <number>)
std::string files_of(gfx::shader_source const &Source)
{
std::string result;
for (std::size_t i = 0; i < Source.files.size(); ++i)
result += (i > 0 ? ", " : "") + std::to_string(i) + ": " + Source.files[i];
return result;
}
std::string shader_log(GLuint const Shader)
{
GLint length{0};
glGetShaderiv(Shader, GL_INFO_LOG_LENGTH, &length);
std::string info(static_cast<std::size_t>(std::max(length, 1)), '\0');
glGetShaderInfoLog(Shader, static_cast<GLsizei>(info.size()), nullptr, info.data());
info.resize(std::strlen(info.c_str()));
return info;
}
std::string program_log(GLuint const Program)
{
GLint length{0};
glGetProgramiv(Program, GL_INFO_LOG_LENGTH, &length);
std::string info(static_cast<std::size_t>(std::max(length, 1)), '\0');
glGetProgramInfoLog(Program, static_cast<GLsizei>(info.size()), nullptr, info.data());
info.resize(std::strlen(info.c_str()));
return info;
}
} // namespace
bool gl::shader_binaries_enabled()
{
// decided once, at the first shader: the settings and the driver don't change while the renderer runs
static bool const enabled{[]() {
if (false == Global.gfx_shader_binaries)
{
WriteLog("shaders: compiled from the sources (gfx.shaders.binaries no)", logtype::shader);
return false;
}
if ((false == GLAD_GL_VERSION_4_6) || (glSpecializeShader == nullptr))
{
WriteLog("shaders: the driver can't load SPIR-V, compiled from the sources", logtype::shader);
return false;
}
std::vector<std::string> differences;
if (false == glsl_common_matches({}, differences))
{
std::string text;
for (auto const &difference : differences)
text += "\n " + difference;
ErrorLog("shaders: the constants in " + gfx::shader_source_dir(gfx::shader_api::opengl46) + "common.glsl aren't those of the game, so the SPIR-V binaries of " +
gfx::shader_binary_dir(gfx::shader_api::opengl46) + " can't be used; the shaders are compiled from the sources:" + text,
logtype::shader);
return false;
}
WriteLog("shaders: the SPIR-V binaries of " + gfx::shader_binary_dir(gfx::shader_api::opengl46) + " are loaded where there are up to date ones", logtype::shader);
return true;
}()};
return enabled;
}
gl::shader::shader(const std::string &filename, const std::string &defines) : name(filename), m_defines(defines)
{
auto const source{preprocess(filename, defines, true)};
texture_conf = source.texture_conf;
param_conf = source.param_conf;
m_type = shader_type(source.stage);
if (shader_binaries_enabled() && load_binary(source))
{
if (false == source.has_source)
ErrorLog("shader " + filename + ": the SPIR-V binary is used without its source, so the textures and the parameters of the materials (#pragma texture, #pragma param) aren't known", logtype::shader);
return;
}
if (false == source.has_source)
throw shader_exception("cannot read shader: " + gfx::shader_source_dir(gfx::shader_api::opengl46) + filename);
**this = compile_source(source);
}
bool gl::shader::load_binary(gfx::shader_source const &Source)
{
std::string reason;
auto const words{gfx::load_shader_binary(Source, reason)};
if (false == reason.empty())
WriteLog("shader " + name + (Source.variant.empty() ? "" : " (" + Source.variant + ")") + ": " + reason + ", compiled from the source", logtype::shader);
if (words.empty())
return false;
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
}
std::string gl::shader::read_file(const std::string &filename) const
{
std::stringstream stream;
std::ifstream f;
f.open(filename);
stream << f.rdbuf();
f.close();
std::string str = stream.str();
if (str.empty())
throw shader_exception("cannot read shader: " + filename);
return str;
}
void gl::shader::expand_includes(std::string &str, const std::string &basedir)
{
size_t start_pos = 0;
std::string magic = "#include";
while ((start_pos = str.find(magic, start_pos)) != std::string::npos)
auto const id{glCreateShader(m_type)};
glShaderBinary(1, &id, GL_SHADER_BINARY_FORMAT_SPIR_V, words.data(), static_cast<GLsizei>(words.size() * sizeof(std::uint32_t)));
// the settings the module has, the others aren't given
auto const values{glsl_settings_values()};
std::vector<GLuint> indices;
std::vector<GLuint> settings;
for (auto const id : gfx::spirv_specialization_ids(words))
{
size_t fp = str.find('<', start_pos);
size_t fe = str.find('>', start_pos);
if (fp == std::string::npos || fe == std::string::npos)
return;
std::string filename = str.substr(fp + 1, fe - fp - 1);
std::string content;
if (filename != "common")
content = read_file(basedir + filename);
else
content = glsl_common;
str.replace(start_pos, fe - start_pos + 1, content);
if (id < values.size())
{
indices.push_back(id);
settings.push_back(values[id]);
}
}
}
glSpecializeShader(id, "main", static_cast<GLuint>(indices.size()), indices.data(), settings.data());
std::unordered_map<std::string, gl::shader::components_e> gl::shader::components_mapping =
{
{ "R", components_e::R },
{ "RG", components_e::RG },
{ "RGB", components_e::RGB },
{ "RGBA", components_e::RGBA },
{ "sRGB", components_e::sRGB },
{ "sRGB_A", components_e::sRGB_A }
};
std::unordered_map<std::string, gl::shader::defaultparam_e> gl::shader::defaultparams_mapping =
{
{ "required", defaultparam_e::required },
{ "nan", defaultparam_e::nan },
{ "zero", defaultparam_e::zero },
{ "one", defaultparam_e::one },
{ "ambient", defaultparam_e::ambient },
{ "diffuse", defaultparam_e::diffuse },
{ "specular", defaultparam_e::specular },
{ "glossiness", defaultparam_e::glossiness }
};
std::pair<GLuint, std::string> gl::shader::process_source(const std::string &filename, const std::string &basedir, const std::string &defines)
{
std::string str;
GLuint type;
if (strcend(filename, ".vert"))
type = GL_VERTEX_SHADER;
else if (strcend(filename, ".frag"))
type = GL_FRAGMENT_SHADER;
else if (strcend(filename, ".geom"))
type = GL_GEOMETRY_SHADER;
else if (strcend(filename, ".comp"))
type = GL_COMPUTE_SHADER;
else
throw shader_exception("unknown shader " + filename);
str += "#version 460 core\n";
// expose the shader stage to glsl_common so vertex-only built-ins
// (gl_InstanceID, effective_modelview, etc.) can be guarded out of
// fragment/geometry compilations.
if (type == GL_VERTEX_SHADER) str += "#define STAGE_VERTEX 1\n";
else if (type == GL_FRAGMENT_SHADER) str += "#define STAGE_FRAGMENT 1\n";
else if (type == GL_GEOMETRY_SHADER) str += "#define STAGE_GEOMETRY 1\n";
else if (type == GL_COMPUTE_SHADER) str += "#define STAGE_COMPUTE 1\n";
str += "vec4 FBOUT(vec4 x) { return " + (Global.gfx_shadergamma ? std::string("vec4(pow(x.rgb, vec3(1.0 / 2.2)), x.a)") : std::string("x")) + "; }\n";
// whether the colour written to the window is encoded by the shader (FBOUT) or left to the sRGB framebuffer (dither.glsl)
str += std::string("#define OUTPUT_GAMMA ") + (Global.gfx_shadergamma ? "1" : "0") + "\n";
str += defines;
str += read_file(basedir + filename);
expand_includes(str, basedir);
parse_texture_entries(str);
parse_param_entries(str);
return std::make_pair(type, str);
}
void gl::shader::parse_texture_entries(std::string &str)
{
size_t start_pos = 0;
std::string magic = "#texture";
while ((start_pos = str.find(magic, start_pos)) != std::string::npos)
GLint status{GL_FALSE};
glGetShaderiv(id, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE)
{
size_t fp = str.find('(', start_pos);
size_t fe = str.find(')', start_pos);
if (fp == std::string::npos || fe == std::string::npos)
return;
std::istringstream ss(str.substr(fp + 1, fe - fp - 1));
std::string token;
std::string uniformname;
texture_entry conf;
size_t arg = 0;
while (std::getline(ss, token, ','))
{
std::istringstream token_ss(token);
if (arg == 0)
token_ss >> uniformname;
else if (arg == 1)
token_ss >> conf.id;
else if (arg == 2)
{
std::string comp;
token_ss >> comp;
if (!components_mapping.contains(comp))
log_error("unknown components: " + comp);
else
conf.components = components_mapping[comp];
}
arg++;
}
if (arg == 3)
{
if (uniformname.empty())
log_error("empty name");
else if (conf.id >= gl::MAX_TEXTURES)
log_error("invalid texture binding: " + std::to_string(conf.id));
else
texture_conf.try_emplace(uniformname, conf);
}
else
log_error("invalid argument count to #texture");
str.erase(start_pos, fe - start_pos + 1);
WriteLog("shader " + name + ": the SPIR-V binary can't be loaded (" + shader_log(id) + "), compiled from the source", logtype::shader);
glDeleteShader(id);
return false;
}
**this = id;
m_binary = true;
return true;
}
void gl::shader::parse_param_entries(std::string &str)
GLuint gl::shader::compile_source(gfx::shader_source const &Source) const
{
size_t start_pos = 0;
const GLchar *cstr = Source.text.c_str();
std::string magic = "#param";
while ((start_pos = str.find(magic, start_pos)) != std::string::npos)
{
size_t fp = str.find('(', start_pos);
size_t fe = str.find(')', start_pos);
if (fp == std::string::npos || fe == std::string::npos)
return;
std::istringstream ss(str.substr(fp + 1, fe - fp - 1));
std::string token;
std::string uniformname;
param_entry conf;
size_t arg = 0;
while (std::getline(ss, token, ','))
{
std::istringstream token_ss(token);
if (arg == 0)
token_ss >> uniformname;
else if (arg == 1)
token_ss >> conf.location;
else if (arg == 2)
token_ss >> conf.offset;
else if (arg == 3)
token_ss >> conf.size;
else if (arg == 4)
{
std::string tok;
token_ss >> tok;
if (!defaultparams_mapping.contains(tok))
log_error("unknown param default: " + tok);
conf.defaultparam = defaultparams_mapping[tok];
}
arg++;
}
if (arg == 5)
{
if (uniformname.empty())
log_error("empty name");
else if (conf.location >= gl::MAX_PARAMS)
log_error("invalid param binding: " + std::to_string(conf.location));
else if (conf.offset > 3)
log_error("invalid offset: " + std::to_string(conf.offset));
else if (conf.offset + conf.size > 4)
log_error("invalid size: " + std::to_string(conf.size));
else
param_conf.try_emplace(uniformname, conf);
}
else
log_error("invalid argument count to #param");
str.erase(start_pos, fe - start_pos + 1);
}
}
void gl::shader::log_error(const std::string &str) const
{
ErrorLog("bad shader: " + name + ": " + str, logtype::shader);
}
gl::shader::shader(const std::string &filename, const std::string &defines) : name(filename)
{
auto [shadertype, shadersource] = process_source(filename, "shaders/", defines);
const GLchar *cstr = shadersource.c_str();
**this = glCreateShader(shadertype);
glShaderSource(*this, 1, &cstr, nullptr);
glCompileShader(*this);
auto const id{glCreateShader(m_type)};
glShaderSource(id, 1, &cstr, nullptr);
glCompileShader(id);
GLint status;
glGetShaderiv(*this, GL_COMPILE_STATUS, &status);
glGetShaderiv(id, GL_COMPILE_STATUS, &status);
if (!status)
{
std::array<GLchar, 512> info{};
glGetShaderInfoLog(*this, static_cast<GLsizei>(info.size()), nullptr, info.data());
log_error(std::string(info.data()));
throw shader_exception("failed to compile " + filename + ": " + std::string(info.data()));
auto const info{shader_log(id)};
glDeleteShader(id);
ErrorLog("bad shader: " + name + ": " + info + "(source strings: " + files_of(Source) + ")", logtype::shader);
throw shader_exception("failed to compile " + name + ": " + info);
}
return id;
}
GLuint gl::shader::source_object() const
{
if (false == m_binary)
return *this;
if (m_source_object == 0)
m_source_object = compile_source(preprocess(name, m_defines, false));
return m_source_object;
}
gl::shader::~shader()
{
#ifndef SHADERVALIDATOR_STANDALONE
if (m_source_object != 0)
glDeleteShader(m_source_object);
glDeleteShader(*this);
#endif
}
void gl::program::init()
{
bind();
// the samplers and the blocks have their bindings in the shaders; the sources which don't say them (older ones) are given
// them here. a program linked from SPIR-V doesn't know the names, nothing is found then
for (auto const &[texturename, textureentry] : texture_conf)
{
shader::texture_entry const &e = textureentry;
GLuint loc = glGetUniformLocation(*this, texturename.c_str());
glUniform1i(loc, static_cast<GLint>(e.id));
GLint loc = glGetUniformLocation(*this, texturename.c_str());
if (loc >= 0)
glUniform1i(loc, static_cast<GLint>(textureentry.id));
}
glUniform1i(glGetUniformLocation(*this, "shadowmap"), gl::SHADOW_TEX);
glUniform1i(glGetUniformLocation(*this, "envmap"), gl::ENV_TEX);
glUniform1i(glGetUniformLocation(*this, "headlightmap"), gl::HEADLIGHT_TEX);
auto const unit = [this](char const *Name, std::size_t const Unit) {
GLint loc = glGetUniformLocation(*this, Name);
if (loc >= 0)
glUniform1i(loc, static_cast<GLint>(Unit));
};
unit("shadowmap", gl::SHADOW_TEX);
unit("envmap", gl::ENV_TEX);
unit("headlightmap", gl::HEADLIGHT_TEX);
// the prefiltered environment map (envmapping.glsl); without a unit of its own the cube sampler would share unit 0 with
// the 2d diffuse texture of the materials, which makes their draws fail (GL_INVALID_OPERATION)
glUniform1i(glGetUniformLocation(*this, "envmap_ggx"), gl::ENV_GGX_TEX);
unit("envmap_ggx", gl::ENV_GGX_TEX);
GLuint index;
if ((index = glGetUniformBlockIndex(*this, "scene_ubo")) != GL_INVALID_INDEX)
glUniformBlockBinding(*this, index, 0);
glUniformBlockBinding(*this, index, gl::SCENE_UBO_BINDING);
if ((index = glGetUniformBlockIndex(*this, "model_ubo")) != GL_INVALID_INDEX)
glUniformBlockBinding(*this, index, 1);
glUniformBlockBinding(*this, index, gl::MODEL_UBO_BINDING);
}
gl::program::program()
@@ -307,21 +247,51 @@ void gl::program::attach(const gl::shader &s)
texture_conf.try_emplace(texturename, std::move(textureentry));
for (auto [paramname, paramentry] : s.param_conf)
param_conf.try_emplace(paramname, std::move(paramentry));
glAttachShader(*this, *s);
m_attached.push_back(&s);
}
void gl::program::link()
{
glLinkProgram(*this);
GLint status;
glGetProgramiv(*this, GL_LINK_STATUS, &status);
if (!status)
// a program is linked either from SPIR-V binaries or from sources only: when one of the shaders has no binary, the others are
// compiled from the source too
auto const allbinary{std::all_of(m_attached.begin(), m_attached.end(), [](shader const *Shader) { return Shader->is_binary(); })};
GLint status{GL_FALSE};
if (allbinary && (false == m_attached.empty()))
{
std::array<GLchar, 512> info{};
glGetProgramInfoLog(*this, static_cast<GLsizei>(info.size()), nullptr, info.data());
throw shader_exception("failed to link program: " + std::string(info.data()));
for (auto const *s : m_attached)
glAttachShader(*this, *s);
glLinkProgram(*this);
glGetProgramiv(*this, GL_LINK_STATUS, &status);
for (auto const *s : m_attached)
glDetachShader(*this, *s);
if (status == GL_TRUE)
{
m_binary = true;
}
else
{
std::string names;
for (auto const *s : m_attached)
names += (names.empty() ? "" : ", ") + s->name;
WriteLog("program of " + names + ": the SPIR-V binaries can't be linked (" + program_log(*this) + "), compiled from the sources", logtype::shader);
}
}
if (status == GL_FALSE)
{
std::vector<GLuint> objects;
for (auto const *s : m_attached)
objects.push_back(s->source_object());
for (auto const object : objects)
glAttachShader(*this, object);
glLinkProgram(*this);
glGetProgramiv(*this, GL_LINK_STATUS, &status);
for (auto const object : objects)
glDetachShader(*this, object);
m_binary = false;
}
m_attached.clear();
if (status == GL_FALSE)
throw shader_exception("failed to link program: " + program_log(*this));
init();
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include <array>
#include <string>
#include <vector>
#include <functional>
@@ -7,82 +8,63 @@
#include "object.h"
#include "bindable.h"
#include "rendering/common/shadersource.h"
namespace gl
{
class shader_exception : public std::runtime_error
using shader_exception = gfx::shader_exception;
// the settings of the game the shaders take (shaders/ogl46/settings.glsl): the ids of the specialization constants of the
// SPIR-V binaries; compiled from the source, the shaders get them as constants (gl::glsl_settings_defines())
enum class shader_setting : GLuint
{
using runtime_error::runtime_error;
shadowmap = 0,
envmap = 1,
motionblur = 2,
postfx = 3,
extraeffects = 4,
output_gamma = 5,
count = 6
};
// a shader of shaders/ogl46/: the SPIR-V binary of shaders/ogl46bin/ when there's an up to date one (gfx.shaders.binaries),
// compiled from the source otherwise
class shader : public object
{
public:
#ifdef SHADERVALIDATOR_STANDALONE
shader() = default;
#endif
// Defines: lines put before the source (after the version), e.g. "#define MODEL_DRAWS 1\n" for a variant of the shader
explicit shader(const std::string &filename, const std::string &defines = {});
~shader();
enum class components_e
{
R = GL_RED,
RG = GL_RG,
RGB = GL_RGB,
RGBA = GL_RGBA,
sRGB = GL_SRGB,
sRGB_A = GL_SRGB_ALPHA
};
using components_e = gfx::shader_components;
using texture_entry = gfx::shader_texture_entry;
using defaultparam_e = gfx::shader_param_default;
using param_entry = gfx::shader_param_entry;
struct texture_entry
{
size_t id;
components_e components;
};
enum class defaultparam_e
{
required,
nan,
zero,
one,
ambient,
diffuse,
specular,
glossiness
};
struct param_entry
{
size_t location;
size_t offset;
size_t size;
defaultparam_e defaultparam;
};
std::unordered_map<std::string, texture_entry> texture_conf;
std::unordered_map<std::string, param_entry> param_conf;
gfx::shader_texture_conf texture_conf;
gfx::shader_param_conf param_conf;
std::string name;
#ifndef SHADERVALIDATOR_STANDALONE
// whether the shader was loaded from a SPIR-V binary
bool is_binary() const
{
return m_binary;
}
// the shader compiled from the source: the shader itself, or for a binary one the same shader compiled from the source
// (a program can't be linked from binaries and sources together). throws shader_exception
GLuint source_object() const;
private:
#endif
std::pair<GLuint, std::string> process_source(const std::string &filename, const std::string &basedir, const std::string &defines = {});
bool load_binary(gfx::shader_source const &Source);
GLuint compile_source(gfx::shader_source const &Source) const;
void expand_includes(std::string &str, const std::string &basedir);
void parse_texture_entries(std::string &str);
void parse_param_entries(std::string &str);
std::string read_file(const std::string &filename) const;
static std::unordered_map<std::string, components_e> components_mapping;
static std::unordered_map<std::string, defaultparam_e> defaultparams_mapping;
void log_error(const std::string &str) const;
GLenum m_type{0};
std::string m_defines;
bool m_binary{false};
mutable GLuint m_source_object{0};
};
class program : public object, public bindable<program>
class program : public gfx::shader_program, public object, public bindable<program>
{
public:
program();
@@ -92,16 +74,27 @@ namespace gl
using bindable::bind;
static void bind(GLuint i);
// the shaders are linked by link(), which has to come while they are there
void attach(const shader &);
void link();
// for a program linked from a compute shader only: binds it and runs the given number of work groups
void dispatch(GLuint groups_x, GLuint groups_y = 1, GLuint groups_z = 1);
std::unordered_map<std::string, shader::texture_entry> texture_conf;
std::unordered_map<std::string, shader::param_entry> param_conf;
// whether the program was linked from SPIR-V binaries
bool is_binary() const
{
return m_binary;
}
private:
void init();
std::vector<shader const *> m_attached;
bool m_binary{false};
};
// whether the shaders are loaded from the SPIR-V binaries where there are some: gfx.shaders.binaries, an opengl 4.6 driver,
// and the constants of the game the same as those the binaries were compiled with (gl::glsl_common_matches())
bool shader_binaries_enabled();
}

View File

@@ -247,7 +247,11 @@ namespace gl
const GLuint CLUSTER_BUILD_GROUP_SIZE = 64;
const GLuint CLUSTER_CULL_GROUP_SIZE = 64;
// binding points of the cluster buffers: layout(binding = ...) in glsl_common.
// binding points of the blocks of the scene and of the model being drawn (common.glsl)
const GLuint SCENE_UBO_BINDING = 0;
const GLuint MODEL_UBO_BINDING = 1;
// binding points of the cluster buffers: layout(binding = ...) in common.glsl.
// the uniform block numbers go together with scene (0), model (1) and instance (3) blocks
const GLuint CLUSTER_UBO_BINDING = 2;
const GLuint CLUSTER_AABB_SSBO_BINDING = 1;

View File

@@ -16,9 +16,9 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/translation.h"
#include "rendering/renderer.h"
#include "stb/stb_image.h"
#include <glad/glad.h>
#include <algorithm>
#include <chrono>
@@ -100,8 +100,8 @@ ui::scenerywizard_panel::~scenerywizard_panel()
{
if (m_request.valid())
m_request.wait();
if (m_texture != 0)
glDeleteTextures(1, &m_texture);
if ((m_texture != 0) && (GfxRenderer != nullptr) && (GfxRenderer->GetImguiRenderer() != nullptr))
GfxRenderer->GetImguiRenderer()->Release_Image(m_texture);
}
void ui::scenerywizard_panel::render_contents()
@@ -245,16 +245,11 @@ void ui::scenerywizard_panel::upload_map()
return;
}
m_map_error.clear();
if (m_texture == 0)
glGenTextures(1, &m_texture);
glBindTexture(GL_TEXTURE_2D, m_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, result.width, result.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, result.pixels.data());
glBindTexture(GL_TEXTURE_2D, 0);
// the map as the service sends it, its values taken as they are (not as srgb), with the linear filtering
imgui_renderer::image_options options;
options.srgb = false;
if (auto *renderer{GfxRenderer->GetImguiRenderer()})
m_texture = renderer->Update_Image(m_texture, result.pixels.data(), result.width, result.height, options);
m_image_low = result.low;
m_image_high = result.high;
}

View File

@@ -54,7 +54,7 @@ class scenerywizard_panel : public ui_panel
bool m_dragging{false};
std::future<map_result> m_request;
unsigned int m_texture{0};
std::uint64_t m_texture{0}; // imgui_renderer image
glm::dvec2 m_image_low{0.0};
glm::dvec2 m_image_high{0.0};
std::string m_map_error;

View File

@@ -43,7 +43,7 @@ struct texture_request {
int height { 0 };
int mapcount { 0 };
int blocksize { 0 };
// handed to the workers (opengl_texture::prefetch()), and when: the main thread only
// handed to the workers (texture_source::prefetch()), and when: the main thread only
bool submitted { false };
std::chrono::steady_clock::time_point submittime;
bool ahead { false }; // read ahead of the camera, which was at the origin then
@@ -138,7 +138,7 @@ private:
m_read.notify_all();
} }
// same as the reading part of opengl_texture::load_DDS()
// same as the reading part of texture_source::load_DDS()
static void read( texture_request &Request ) {
std::ifstream file( Request.file, std::ios::binary );
if( false == file.is_open() ) {
@@ -182,7 +182,7 @@ private:
std::array<GLuint, gl::MAX_TEXTURES + gl::HELPER_TEXTURES> opengl_texture::units = { 0 };
GLint opengl_texture::m_activeunit = -1;
std::unordered_map<GLint, int> opengl_texture::precompressed_formats =
std::unordered_map<GLint, int> texture_source::precompressed_formats =
{
{ GL_COMPRESSED_RGBA8_ETC2_EAC, 16 },
{ GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, 16 },
@@ -257,16 +257,25 @@ std::unordered_map<GLint, std::unordered_map<GLint, GLint>> opengl_texture::mapp
{ GL_RED, GL_R8 } } },
};
texture_manager::texture_manager() {
texture_bank::texture_bank( std::function<texture_source *()> Make ) :
m_make( std::move( Make ) ) {
// since index 0 is used to indicate no texture, we put a blank entry in the first texture slot
m_textures.emplace_back( new opengl_texture(), std::chrono::steady_clock::time_point() );
m_textures.emplace_back( m_make(), std::chrono::steady_clock::time_point() );
}
texture_bank::~texture_bank() {
delete_textures();
}
texture_manager::texture_manager() :
texture_bank( []() -> texture_source * { return new opengl_texture(); } ) {}
// loads texture data from specified file
// TODO: wrap it in a workitem class, for the job system deferred loading
void
opengl_texture::load() {
texture_source::load() {
if (data_state == resource_state::good)
return;
@@ -319,11 +328,11 @@ fail:
data_state = resource_state::failed;
ErrorLog( "Bad texture: failed to load texture \"" + name + "\"" );
// NOTE: temporary workaround for texture assignment errors
id = 0;
on_failed();
return;
}
void opengl_texture::load_PNG()
void texture_source::load_PNG()
{
//png_image png;
//memset(&png, 0, sizeof(png_image));
@@ -370,7 +379,7 @@ void opengl_texture::load_PNG()
//data_state = resource_state::good;
}
void opengl_texture::load_STBI()
void texture_source::load_STBI()
{
int x, y, n;
stbi_set_flip_vertically_on_load(1);
@@ -395,7 +404,7 @@ void opengl_texture::load_STBI()
}
void
opengl_texture::make_stub()
texture_source::make_stub()
{
data_width = 2;
data_height = 2;
@@ -410,7 +419,7 @@ opengl_texture::make_stub()
}
void
opengl_texture::make_from_memory(size_t width, size_t height, const uint8_t *raw)
texture_source::make_from_memory(size_t width, size_t height, const uint8_t *raw)
{
release();
@@ -467,7 +476,7 @@ void opengl_texture::update_from_memory(size_t width, size_t height, const uint8
}
void
opengl_texture::make_request() {
texture_source::make_request() {
auto const components { Split( name, '?' ) };
@@ -479,7 +488,7 @@ opengl_texture::make_request() {
Application.request( { ToLower( components.front() ), dictionary, rt } );
}
DDCOLORKEY opengl_texture::deserialize_ddck(std::istream &s)
DDCOLORKEY texture_source::deserialize_ddck(std::istream &s)
{
DDCOLORKEY ddck;
@@ -489,7 +498,7 @@ DDCOLORKEY opengl_texture::deserialize_ddck(std::istream &s)
return ddck;
}
DDPIXELFORMAT opengl_texture::deserialize_ddpf(std::istream &s)
DDPIXELFORMAT texture_source::deserialize_ddpf(std::istream &s)
{
DDPIXELFORMAT ddpf;
@@ -505,7 +514,7 @@ DDPIXELFORMAT opengl_texture::deserialize_ddpf(std::istream &s)
return ddpf;
}
DDSCAPS2 opengl_texture::deserialize_ddscaps(std::istream &s)
DDSCAPS2 texture_source::deserialize_ddscaps(std::istream &s)
{
DDSCAPS2 ddsc;
@@ -517,7 +526,7 @@ DDSCAPS2 opengl_texture::deserialize_ddscaps(std::istream &s)
return ddsc;
}
DDSURFACEDESC2 opengl_texture::deserialize_ddsd(std::istream &s)
DDSURFACEDESC2 texture_source::deserialize_ddsd(std::istream &s)
{
DDSURFACEDESC2 ddsd;
@@ -544,7 +553,7 @@ DDSURFACEDESC2 opengl_texture::deserialize_ddsd(std::istream &s)
}
void
opengl_texture::load_DDS() {
texture_source::load_DDS() {
std::ifstream file( name + type, std::ios::binary | std::ios::ate ); file.unsetf( std::ios::skipws );
std::size_t filesize = static_cast<size_t>(file.tellg()); // ios::ate already positioned us at the end of the file
@@ -689,7 +698,7 @@ opengl_texture::load_DDS() {
}
void
opengl_texture::load_KTX() {
texture_source::load_KTX() {
std::ifstream file( name + type, std::ios::binary | std::ios::ate ); file.unsetf( std::ios::skipws );
std::size_t filesize = static_cast<size_t>(file.tellg()); // ios::ate already positioned us at the end of the file
file.seekg( 0, std::ios::beg ); // rewind the caret afterwards
@@ -749,7 +758,7 @@ opengl_texture::load_KTX() {
}
void
opengl_texture::load_TEX() {
texture_source::load_TEX() {
std::ifstream file( name + type, std::ios::binary ); file.unsetf( std::ios::skipws );
@@ -796,7 +805,7 @@ opengl_texture::load_TEX() {
}
void
opengl_texture::load_TGA() {
texture_source::load_TGA() {
std::ifstream file( name + type, std::ios::binary ); file.unsetf( std::ios::skipws );
@@ -1121,7 +1130,7 @@ opengl_texture::create( bool const Static ) {
// takes over the data read by a worker thread, waiting for it if needed
void
opengl_texture::complete_data() {
texture_source::complete_data() {
if( request == nullptr ) { return; }
@@ -1134,7 +1143,7 @@ opengl_texture::complete_data() {
data_state = resource_state::failed;
ErrorLog( "Bad texture: failed to load texture \"" + name + "\"" );
// NOTE: temporary workaround for texture assignment errors, same as in load()
id = 0;
on_failed();
return;
}
data.swap( completed->data );
@@ -1144,10 +1153,10 @@ opengl_texture::complete_data() {
// has a worker thread read the data of the texture, if it's waiting for that. Origin: the camera, for data read ahead of it
void
opengl_texture::prefetch( glm::dvec3 const *Origin ) {
texture_source::prefetch( glm::dvec3 const *Origin ) {
if( ( request == nullptr )
&& ( id == invalid_id )
&& ( false == resident() )
&& ( data_state == resource_state::none )
&& ( true == reload_on_use )
&& ( true == streamed() ) ) {
@@ -1169,7 +1178,7 @@ opengl_texture::prefetch( glm::dvec3 const *Origin ) {
// the data of the texture is read from its file by worker threads when it's needed (gfx.textures.streaming), and not kept
// after the upload: it can be read again
bool
opengl_texture::streamed() const {
texture_source::streamed() const {
return ( ( true == Global.gfx_texture_streaming )
&& ( type == ".dds" )
@@ -1256,7 +1265,7 @@ opengl_texture::alloc_rendertarget( GLint format, GLint components, int width, i
}
void
opengl_texture::set_components_hint( GLint hint ) {
texture_source::set_components_hint( GLint hint ) {
components_hint = hint;
}
@@ -1284,7 +1293,7 @@ opengl_texture::set_filtering() const
}
void
opengl_texture::downsize( GLuint const Format ) {
texture_source::downsize( GLuint const Format ) {
while( data_width > Global.CurrentMaxTextureSize || data_height > Global.CurrentMaxTextureSize ) {
// scale down the base texture, if it's larger than allowed maximum
@@ -1313,7 +1322,7 @@ opengl_texture::downsize( GLuint const Format ) {
}
void
opengl_texture::flip_vertical() {
texture_source::flip_vertical() {
auto const swapsize { data_width * 4 };
auto destination { data.begin() + ( data_height - 1 ) * swapsize };
@@ -1338,7 +1347,7 @@ texture_manager::unit( GLint const Textureunit ) {
// ustalenie numeru tekstury, wczytanie jeśli jeszcze takiej nie było
texture_handle
texture_manager::create( std::string Filename, bool const Loadnow, GLint Formathint ) {
texture_bank::create( std::string Filename, bool const Loadnow, GLint Formathint ) {
if( contains( Filename, '|' ) ) {
Filename.erase( Filename.find( '|' ) ); // po | może być nazwa kolejnej tekstury
@@ -1411,7 +1420,7 @@ texture_manager::create( std::string Filename, bool const Loadnow, GLint Formath
}
}
auto texture = new opengl_texture();
auto texture = m_make();
texture->name = locator.first;
texture->type = locator.second;
texture->traits = traits;
@@ -1424,9 +1433,9 @@ texture_manager::create( std::string Filename, bool const Loadnow, GLint Formath
if( true == Loadnow ) {
texture_manager::texture( textureindex ).load();
texture_bank::texture( textureindex ).load();
#ifndef EU07_DEFERRED_TEXTURE_UPLOAD
texture_manager::texture( textureindex ).create();
texture_bank::texture( textureindex ).create();
// texture creation binds a different texture, force a re-bind on next use
m_activetexture = -1;
#endif
@@ -1446,8 +1455,8 @@ texture_manager::bind( std::size_t const Unit, texture_handle const Texture ) {
opengl_texture::unbind(Unit);
}
opengl_texture &
texture_manager::mark_as_used(const texture_handle Texture) {
texture_source &
texture_bank::mark_as_used(const texture_handle Texture) {
auto &pair = m_textures[ Texture ];
pair.second = m_garbagecollector.timestamp();
@@ -1455,20 +1464,34 @@ texture_manager::mark_as_used(const texture_handle Texture) {
}
void
texture_manager::delete_textures() {
texture_bank::delete_textures() {
for( auto const &texture : m_textures ) {
// usunięcie wszyskich tekstur (bez usuwania struktury)
if( texture.first->id > 0
&& texture.first->id != -1 ) {
::glDeleteTextures( 1, &texture.first->id );
}
texture.first->destroy();
delete texture.first;
}
m_textures.clear();
}
void
opengl_texture::destroy() {
if( id > 0
&& id != -1 ) {
::glDeleteTextures( 1, &id );
}
}
void
opengl_texture::on_failed() {
// NOTE: temporary workaround for texture assignment errors: the texture counts as made, with no gl texture
id = 0;
}
// starts a scan for gl textures used only by vehicles far away
void
texture_manager::begin_release_scan() {
texture_bank::begin_release_scan() {
m_releasescantime = std::chrono::steady_clock::now();
// textures bound from now on are stamped with the time of this scan, the sweep may not be running
@@ -1480,7 +1503,7 @@ texture_manager::begin_release_scan() {
// marks texture of a vehicle as needed if the vehicle is near, otherwise only as one used by vehicles
void
texture_manager::mark_as_needed( texture_handle const Texture, bool const Near ) {
texture_bank::mark_as_needed( texture_handle const Texture, bool const Near ) {
if( ( Texture <= 0 )
|| ( Texture >= static_cast<texture_handle>( m_neededtimes.size() ) ) ) {
@@ -1506,7 +1529,7 @@ texture_manager::mark_as_needed( texture_handle const Texture, bool const Near )
// releases gl textures of vehicles not needed nor drawn for a while
void
texture_manager::release_unneeded() {
texture_bank::release_unneeded() {
// the delay keeps textures of a vehicle moving back and forth around the release distance from going in and out
auto const delay { std::chrono::seconds{ 10 } };
@@ -1520,8 +1543,8 @@ texture_manager::release_unneeded() {
}
auto &record { m_textures[ index ] };
auto &texture { *record.first };
if( ( texture.id == static_cast<GLuint>( -1 ) )
|| ( texture.id == 0 )
if( ( false == texture.resident() )
|| ( texture.get_id() == 0 )
|| ( true == texture.is_static )
|| ( true == texture.is_rendertarget )
|| ( texture.type == "make:" )
@@ -1539,26 +1562,30 @@ texture_manager::release_unneeded() {
}
if( releasecount > 0 ) {
// released gl names can be given to new textures, the unit cache can't trust them
for( auto &unit : opengl_texture::units ) {
unit = -1;
}
released();
}
}
// performs a resource sweep
void
texture_manager::update() {
texture_bank::update() {
if( m_garbagecollector.sweep() > 0 ) {
for( auto &unit : opengl_texture::units ) {
unit = -1;
}
released();
}
}
void
texture_manager::released() {
for( auto &unit : opengl_texture::units ) {
unit = -1;
}
}
// what the textures keep in memory, for the debug panel
texture_manager::memory_data
texture_manager::memory() const {
texture_bank::memory_data
texture_bank::memory() const {
memory_data result;
for( auto const &texture : m_textures ) {
@@ -1567,7 +1594,7 @@ texture_manager::memory() const {
++result.cpu.count;
result.cpu.bytes += memory_of( texture.first->data );
}
if( texture.first->id != opengl_texture::invalid_id ) {
if( true == texture.first->resident() ) {
++result.gpu.count;
result.gpu.bytes += static_cast<std::uint64_t>( texture.first->size ) * 1024;
}
@@ -1590,7 +1617,7 @@ texture_manager::memory() const {
// has the data of a texture read, if it's waiting for that
void
texture_manager::prefetch( texture_handle const Texture, glm::dvec3 const &Origin ) {
texture_bank::prefetch( texture_handle const Texture, glm::dvec3 const &Origin ) {
if( ( Texture <= 0 )
|| ( Texture >= static_cast<texture_handle>( m_textures.size() ) ) ) {
@@ -1602,7 +1629,7 @@ texture_manager::prefetch( texture_handle const Texture, glm::dvec3 const &Origi
// gives back the data read for textures which nothing drew within the specified time since it was asked for, or read ahead of the
// camera which went farther than the specified distance from there since. it's read again when a texture is needed
std::size_t
texture_manager::drop_read_ahead( std::chrono::steady_clock::duration const Age, glm::dvec3 const &Camera, double const Distance ) {
texture_bank::drop_read_ahead( std::chrono::steady_clock::duration const Age, glm::dvec3 const &Camera, double const Distance ) {
auto const now { std::chrono::steady_clock::now() };
std::size_t count { 0 };
@@ -1629,7 +1656,7 @@ texture_manager::drop_read_ahead( std::chrono::steady_clock::duration const Age,
// debug performance string
std::string
texture_manager::info() const {
texture_bank::info() const {
// TODO: cache this data and update only during resource sweep
std::size_t totaltexturecount{ m_textures.size() - 1 };
@@ -1668,7 +1695,7 @@ texture_manager::info() const {
// checks whether specified texture is in the texture bank. returns texture id, or npos.
texture_handle
texture_manager::find_in_databank( std::string const &Texturename ) const {
texture_bank::find_in_databank( std::string const &Texturename ) const {
std::vector<std::string> const filenames {
Global.asCurrentTexturePath + Texturename,
@@ -1687,7 +1714,7 @@ texture_manager::find_in_databank( std::string const &Texturename ) const {
// checks whether specified file exists.
std::pair<std::string, std::string>
texture_manager::find_on_disk( std::string const &Texturename ) {
texture_bank::find_on_disk( std::string const &Texturename ) {
std::vector<std::string> const filenames {
Global.asCurrentTexturePath + Texturename,

View File

@@ -15,40 +15,33 @@ http://mozilla.org/MPL/2.0/.
#include "winheaders.h"
#include "utilities/memorystats.h"
#include <string>
#include <functional>
#include "model/ResourceManager.h"
#include "gl/ubo.h"
#include "interfaces/ITexture.h"
struct texture_request; // texture data read by a worker thread, with gfx.textures.streaming
struct opengl_texture : public ITexture {
// a texture as its file gives it, whichever api draws it: read and decoded (dds, tga, png, bmp, jpg, ktx, tex), with the mipmaps
// the file has, kept within maxtexturesize; with gfx.textures.streaming the data of a dds file is read by worker threads. the
// data is laid out as opengl takes it (the bottom row first) and its formats are the values of opengl (GL_RGBA,
// GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ...), which the renderers take as they are. a renderer makes its own object of the texture
// from the data (create())
struct texture_source : public ITexture {
static DDSURFACEDESC2 deserialize_ddsd(std::istream&);
static DDCOLORKEY deserialize_ddck(std::istream&);
static DDPIXELFORMAT deserialize_ddpf(std::istream&);
static DDSCAPS2 deserialize_ddscaps(std::istream&);
// constructors
opengl_texture() = default;
texture_source() = default;
// methods
void
load();
bool
bind( size_t unit );
static void
unbind( size_t unit );
virtual bool
create( bool const Static = false ) override;
// releases resources allocated on the opengl end, storing local copy if requested
void
release() override;
void
make_stub() override;
void
alloc_rendertarget( GLint format, GLint components, int width, int height, int layers = 1, int samples = 1, GLint wrap = GL_CLAMP_TO_EDGE );
virtual void
set_components_hint( GLint hint ) override;
static void
reset_unit_cache();
virtual int
get_width() const override {
return data_width; }
@@ -57,10 +50,6 @@ struct opengl_texture : public ITexture {
get_height() const override {
return data_height; }
virtual
size_t
get_id() const override {
return id; }
virtual
bool
is_stub() const override {
return is_texstub; }
@@ -86,11 +75,15 @@ struct opengl_texture : public ITexture {
return type; }
virtual void make_from_memory(size_t width, size_t height, const uint8_t *data) override;
virtual void update_from_memory(size_t width, size_t height, const uint8_t *data) override;
// whether the texture has its object made by the renderer (it may be the failed one, with the id 0)
virtual bool
resident() const = 0;
// the object of the renderer goes, with the bank of the textures
virtual void
destroy() = 0;
// members
static constexpr GLuint invalid_id { std::numeric_limits<GLuint>::max() }; // id value of texture without GL resource
GLuint id{ invalid_id }; // associated GL resource
bool has_alpha{ false }; // indicates the texture has alpha channel
bool is_ready{ false }; // indicates the texture was processed and is ready for use
std::string traits; // requested texture attributes: wrapping modes etc
@@ -99,10 +92,6 @@ struct opengl_texture : public ITexture {
std::size_t size{ 0 }; // size of the texture data, in kb
GLint components_hint = 0; // components that material wants
GLenum target = GL_TEXTURE_2D;
static std::array<GLuint, gl::MAX_TEXTURES + gl::HELPER_TEXTURES> units;
static GLint m_activeunit;
public:
// methods
void make_request();
@@ -112,7 +101,6 @@ public:
void load_TEX();
void load_STBI();
void load_TGA();
void set_filtering() const;
void downsize( GLuint const Format );
void flip_vertical();
@@ -129,8 +117,6 @@ public:
bool reload_on_use = false; // the data was dropped along with the gl texture of a far texture, it's read again when the texture is needed
bool is_static = false; // is excluded from garbage collection
bool is_rendertarget = false; // is used as postfx rendertarget, without loaded data
int samples = 1;
int layers = 1;
bool is_texstub = false; // for make_from_memory internal_src: functionality
std::vector<unsigned char> data; // texture data (stored GL-style, bottom-left origin)
resource_state data_state{ resource_state::none }; // current state of texture data
@@ -140,36 +126,90 @@ public:
GLint data_format{ 0 },
data_components{ 0 };
GLint data_type = GL_UNSIGNED_BYTE;
GLint wrap_mode_s = GL_REPEAT;
GLint wrap_mode_t = GL_REPEAT;
/*
std::atomic<bool> is_loaded{ false }; // indicates the texture data was loaded and can be processed
std::atomic<bool> is_good{ false }; // indicates the texture data was retrieved without errors
*/
// the compressed formats the files give, with the bytes of their blocks of 4x4 texels
static std::unordered_map<GLint, int> precompressed_formats;
static std::unordered_map<GLint, GLint> drivercompressed_formats;
static std::unordered_map<GLint, std::unordered_map<GLint, GLint>> mapping;
protected:
// the data of the texture couldn't be read (load(), complete_data())
virtual void
on_failed() {}
};
class texture_manager {
// a texture of the opengl renderer
struct opengl_texture : public texture_source {
// constructors
opengl_texture() = default;
// methods
bool
bind( size_t unit );
static void
unbind( size_t unit );
virtual bool
create( bool const Static = false ) override;
// releases resources allocated on the opengl end, storing local copy if requested
void
release() override;
void
alloc_rendertarget( GLint format, GLint components, int width, int height, int layers = 1, int samples = 1, GLint wrap = GL_CLAMP_TO_EDGE );
static void
reset_unit_cache();
virtual
size_t
get_id() const override {
return id; }
virtual void update_from_memory(size_t width, size_t height, const uint8_t *data) override;
bool
resident() const override {
return id != invalid_id; }
void
destroy() override;
// members
static constexpr GLuint invalid_id { std::numeric_limits<GLuint>::max() }; // id value of texture without GL resource
GLuint id{ invalid_id }; // associated GL resource
GLenum target = GL_TEXTURE_2D;
static std::array<GLuint, gl::MAX_TEXTURES + gl::HELPER_TEXTURES> units;
static GLint m_activeunit;
public:
texture_manager();
~texture_manager() { delete_textures(); }
// methods
void set_filtering() const;
// activates specified texture unit
// members
int samples = 1;
int layers = 1;
GLint wrap_mode_s = GL_REPEAT;
GLint wrap_mode_t = GL_REPEAT;
static std::unordered_map<GLint, GLint> drivercompressed_formats;
static std::unordered_map<GLint, std::unordered_map<GLint, GLint>> mapping;
protected:
void
unit( GLint const Textureunit );
on_failed() override;
};
// the textures of a renderer: made from their files the first time they are asked for, given back by their handles, released
// when nothing used them for a while (gfx.resource.sweep) or when only vehicles far away use them (gfx.textures.releasedistance).
// the objects of the textures come from the renderer (the function given to the constructor)
class texture_bank {
public:
explicit texture_bank( std::function<texture_source *()> Make );
virtual ~texture_bank();
// creates texture object out of data stored in specified file
texture_handle
create( std::string Filename, bool const Loadnow = true, GLint Formathint = GL_SRGB_ALPHA );
// binds specified texture to specified texture unit
void
bind( std::size_t const Unit, texture_handle const Texture );
opengl_texture &
texture_source &
mark_as_used( texture_handle const Texture );
// provides direct access to specified texture object
opengl_texture &
texture_source &
texture( texture_handle const Texture ) const { return *m_textures[Texture].first; }
// performs a resource sweep
void
@@ -208,10 +248,15 @@ public:
std::size_t
drop_read_ahead( std::chrono::steady_clock::duration const Age, glm::dvec3 const &Camera, double const Distance );
protected:
// objects of textures were released, the renderer can't trust what it knew of their names
virtual void
released() {}
private:
// types:
typedef std::pair<
opengl_texture *,
texture_source *,
resource_timestamp > texturetimepoint_pair;
typedef std::vector< texturetimepoint_pair > texturetimepointpair_sequence;
@@ -233,6 +278,7 @@ private:
delete_textures();
// members:
std::function<texture_source *()> m_make;
texture_handle const npos { 0 }; // should be -1, but the rest of the code uses -1 for something else
texturetimepointpair_sequence m_textures;
index_map m_texturemappings;
@@ -241,6 +287,31 @@ private:
garbage_collector<texturetimepointpair_sequence> m_garbagecollector { m_textures, 600, 60, "texture" };
};
// the textures of the opengl renderer
class texture_manager : public texture_bank {
public:
texture_manager();
// activates specified texture unit
void
unit( GLint const Textureunit );
// binds specified texture to specified texture unit
void
bind( std::size_t const Unit, texture_handle const Texture );
opengl_texture &
mark_as_used( texture_handle const Texture ) {
return static_cast<opengl_texture &>( texture_bank::mark_as_used( Texture ) ); }
// provides direct access to specified texture object
opengl_texture &
texture( texture_handle const Texture ) const {
return static_cast<opengl_texture &>( texture_bank::texture( Texture ) ); }
protected:
void
released() override;
};
// reduces provided data image to half of original size, using basic 2x2 average
template <typename Colortype_>
void

View File

@@ -204,7 +204,7 @@ void opengl_material::finalize(bool Loadnow)
key.pop_back();
if (shader->param_conf.find(key) != shader->param_conf.end())
{
gl::shader::param_entry entry = shader->param_conf[key];
gfx::shader_param_entry entry = shader->param_conf[key];
for (size_t i = 0; i < entry.size; i++)
params[entry.location][entry.offset + i] = value[i];
}
@@ -242,17 +242,17 @@ void opengl_material::finalize(bool Loadnow)
for (auto it : shader->param_conf)
{
gl::shader::param_entry entry = it.second;
gfx::shader_param_entry entry = it.second;
if (std::isnan(params[entry.location][entry.offset]))
{
float value = std::numeric_limits<float>::quiet_NaN();
if (entry.defaultparam == gl::shader::defaultparam_e::one)
if (entry.defaultparam == gfx::shader_param_default::one)
value = 1.0f;
else if (entry.defaultparam == gl::shader::defaultparam_e::zero)
else if (entry.defaultparam == gfx::shader_param_default::zero)
value = 0.0f;
else if (entry.defaultparam == gl::shader::defaultparam_e::required)
else if (entry.defaultparam == gfx::shader_param_default::required)
log_error("unspecified required param: " + it.first);
else if (entry.defaultparam != gl::shader::defaultparam_e::nan)
else if (entry.defaultparam != gfx::shader_param_default::nan)
{
params_state.push_back(entry);
continue;
@@ -265,7 +265,7 @@ void opengl_material::finalize(bool Loadnow)
for (auto it : shader->texture_conf)
{
gl::shader::texture_entry &entry = it.second;
gfx::shader_texture_entry &entry = it.second;
texture_handle handle = textures[entry.id];
// NOTE: texture validation at this stage relies on forced texture load behaviour during its create() call
// TODO: move texture id validation to later stage if/when deferred texture loading is implemented
@@ -302,7 +302,7 @@ bool opengl_material::update() {
finalize( true );
result = true;
}
catch( gl::shader_exception const &e ) {
catch( gfx::shader_exception const &e ) {
ErrorLog( "invalid shader: " + std::string( e.what() ) );
}
}
@@ -434,7 +434,7 @@ opengl_material::deserialize_mapping( cParser &Input, int const Priority, bool c
shader = GfxRenderer->Fetch_Shader(value);
m_shader_priority = Priority;
}
catch (gl::shader_exception const &e)
catch (gfx::shader_exception const &e)
{
log_error("invalid shader: " + std::string(e.what()));
}
@@ -591,7 +591,7 @@ material_manager::create( std::string const &Filename, bool const Loadnow ) {
{
material.shader = GfxRenderer->Fetch_Shader("default_1");
}
catch (gl::shader_exception const &e)
catch (gfx::shader_exception const &e)
{
ErrorLog("invalid shader: " + std::string(e.what()));
}
@@ -625,7 +625,7 @@ material_manager::create( std::string const &Filename, bool const Loadnow ) {
materialhandle = m_materials.size();
m_materials.emplace_back( std::move(material) );
m_materialmappings.emplace( m_materials.back().name, materialhandle );
} catch (gl::shader_exception const &e) {
} catch (gfx::shader_exception const &e) {
ErrorLog("invalid shader: " + std::string(e.what()));
// record the failure so subsequent Fetch_Material(filename) calls
// short-circuit without re-running the failing compile.
@@ -652,7 +652,7 @@ material_manager::create_from_text( std::string const &Name, std::string const &
try {
material.finalize( true );
}
catch( gl::shader_exception const &e ) {
catch( gfx::shader_exception const &e ) {
ErrorLog( "invalid shader: " + std::string( e.what() ) );
return null_handle;
}
@@ -709,7 +709,7 @@ material_manager::create_matte( material_handle const Material ) {
matte.params_state.erase(
std::remove_if(
std::begin( matte.params_state ), std::end( matte.params_state ),
[&]( gl::shader::param_entry const &State ) {
[&]( gfx::shader_param_entry const &State ) {
return State.location == entry->second.location && State.offset == entry->second.offset; } ),
std::end( matte.params_state ) );
}
@@ -747,7 +747,7 @@ material_manager::create_tinted( material_handle const Material, glm::vec3 const
tinted.params_state.erase(
std::remove_if(
std::begin( tinted.params_state ), std::end( tinted.params_state ),
[&]( gl::shader::param_entry const &State ) {
[&]( gfx::shader_param_entry const &State ) {
return State.location == location; } ),
std::end( tinted.params_state ) );
auto const tintedhandle { static_cast<material_handle>( m_materials.size() ) };
@@ -832,7 +832,7 @@ material_manager::blend_lighting( material_handle const Material ) {
result = ( fits ? lighting : -1 );
}
}
catch( gl::shader_exception const &e ) {
catch( gfx::shader_exception const &e ) {
ErrorLog( "invalid shader: " + std::string( e.what() ) );
}
}
@@ -914,7 +914,7 @@ material_manager::build_blend( blend_key const &Key, opengl_material &Blend ) {
Blend.finalize( true );
return Blend.is_good;
}
catch( gl::shader_exception const &e ) {
catch( gfx::shader_exception const &e ) {
ErrorLog( "invalid shader: " + std::string( e.what() ) );
}
return false;

View File

@@ -12,7 +12,7 @@ http://mozilla.org/MPL/2.0/.
#include "interfaces/IMaterial.h"
#include "utilities/Classes.h"
#include "model/Texture.h"
#include "gl/shader.h"
#include "rendering/common/shadersource.h"
#include "gl/ubo.h"
// a collection of parameters for the rendering setup.
@@ -26,9 +26,9 @@ struct opengl_material : public IMaterial {
// NOTE: all variants share the remaining material properties, opacity and translucency included
std::vector<texture_set> texture_variants;
std::array<glm::vec4, gl::MAX_PARAMS> params;
std::vector<gl::shader::param_entry> params_state;
std::vector<gfx::shader_param_entry> params_state;
std::shared_ptr<gl::program> shader;
std::shared_ptr<gfx::shader_program> shader;
std::optional<float> opacity;
std::optional<float> selfillum;
float glossiness { 10.f };

1
ref/Vulkan-Headers Submodule

Submodule ref/Vulkan-Headers added at 6802bb4733

1
ref/glslang Submodule

Submodule ref/glslang added at e1b562a8be

1
ref/volk Submodule

Submodule ref/volk added at 54fc0d7a72

View File

@@ -0,0 +1,689 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/common/shadersource.h"
#include <algorithm>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <mutex>
#include <sstream>
#include "utilities/Logs.h"
namespace gfx
{
namespace
{
bool ends_with(std::string_view Value, std::string_view Ending)
{
return (Ending.size() <= Value.size()) && (Value.compare(Value.size() - Ending.size(), Ending.size(), Ending) == 0);
}
// the content of a file, empty with Found false when it can't be read
std::string read_text(std::string const &Path, bool &Found)
{
std::ifstream file(Path, std::ios::binary);
Found = file.is_open();
if (false == Found)
{
return {};
}
std::stringstream stream;
stream << file.rdbuf();
return stream.str();
}
std::string read_file(std::string const &Path)
{
bool found{false};
auto text{read_text(Path, found)};
if ((false == found) || text.empty())
{
throw shader_exception("cannot read shader: " + Path);
}
return text;
}
std::unordered_map<std::string, shader_components> const components_mapping = {
{"R", shader_components::R}, {"RG", shader_components::RG}, {"RGB", shader_components::RGB}, {"RGBA", shader_components::RGBA}, {"sRGB", shader_components::sRGB}, {"sRGB_A", shader_components::sRGB_A}};
std::unordered_map<std::string, shader_param_default> const defaultparams_mapping = {
{"required", shader_param_default::required}, {"nan", shader_param_default::nan}, {"zero", shader_param_default::zero}, {"one", shader_param_default::one},
{"ambient", shader_param_default::ambient}, {"diffuse", shader_param_default::diffuse}, {"specular", shader_param_default::specular}, {"glossiness", shader_param_default::glossiness}};
void log_error(std::string const &Name, std::string const &Message)
{
ErrorLog("bad shader: " + Name + ": " + Message, logtype::shader);
}
// the position of the next directive Magic ("texture", "param") of the materials: #pragma <magic>(...), or the older #<magic> (...)
std::size_t find_directive(std::string const &Text, std::string const &Magic, std::size_t const From, std::size_t &Arguments)
{
auto start{From};
while ((start = Text.find('#', start)) != std::string::npos)
{
auto position{start + 1};
auto const skip_blanks = [&]() {
while ((position < Text.size()) && ((Text[position] == ' ') || (Text[position] == '\t')))
{
++position;
}
};
skip_blanks();
if (Text.compare(position, 6, "pragma") == 0)
{
position += 6;
skip_blanks();
}
if ((Text.compare(position, Magic.size(), Magic) == 0) && (position + Magic.size() < Text.size()))
{
position += Magic.size();
skip_blanks();
if ((position < Text.size()) && (Text[position] == '('))
{
Arguments = position;
return start;
}
}
++start;
}
return std::string::npos;
}
// the arguments of a directive, between the brackets, split at the commas; the directive is taken out of the text
std::vector<std::string> take_directive(std::string &Text, std::size_t const Start, std::size_t const Arguments)
{
std::vector<std::string> result;
auto const end{Text.find(')', Arguments)};
if (end == std::string::npos)
{
Text.erase(Start, 1);
return result;
}
std::istringstream stream(Text.substr(Arguments + 1, end - Arguments - 1));
std::string token;
while (std::getline(stream, token, ','))
{
std::istringstream tokenstream(token);
std::string value;
tokenstream >> value;
result.emplace_back(value);
}
Text.erase(Start, end - Start + 1);
return result;
}
void parse_texture_entries(std::string &Text, shader_source &Source)
{
std::size_t start{0}, arguments{0};
while ((start = find_directive(Text, "texture", start, arguments)) != std::string::npos)
{
auto const args{take_directive(Text, start, arguments)};
if (args.size() != 3)
{
log_error(Source.name, "invalid argument count to #pragma texture");
continue;
}
shader_texture_entry conf{};
try
{
conf.id = static_cast<std::size_t>(std::stoul(args[1]));
}
catch (std::exception const &)
{
log_error(Source.name, "invalid texture binding: " + args[1]);
continue;
}
auto const components{components_mapping.find(args[2])};
if (components == components_mapping.end())
{
log_error(Source.name, "unknown components: " + args[2]);
continue;
}
conf.components = components->second;
if (args[0].empty())
{
log_error(Source.name, "empty name");
}
else if (conf.id >= MAX_SHADER_TEXTURES)
{
log_error(Source.name, "invalid texture binding: " + std::to_string(conf.id));
}
else
{
Source.texture_conf.try_emplace(args[0], conf);
}
}
}
void parse_param_entries(std::string &Text, shader_source &Source)
{
std::size_t start{0}, arguments{0};
while ((start = find_directive(Text, "param", start, arguments)) != std::string::npos)
{
auto const args{take_directive(Text, start, arguments)};
if (args.size() != 5)
{
log_error(Source.name, "invalid argument count to #pragma param");
continue;
}
shader_param_entry conf{};
try
{
conf.location = static_cast<std::size_t>(std::stoul(args[1]));
conf.offset = static_cast<std::size_t>(std::stoul(args[2]));
conf.size = static_cast<std::size_t>(std::stoul(args[3]));
}
catch (std::exception const &)
{
log_error(Source.name, "invalid param: " + args[0]);
continue;
}
auto const defaultparam{defaultparams_mapping.find(args[4])};
if (defaultparam == defaultparams_mapping.end())
{
log_error(Source.name, "unknown param default: " + args[4]);
continue;
}
conf.defaultparam = defaultparam->second;
if (args[0].empty())
{
log_error(Source.name, "empty name");
}
else if (conf.location >= MAX_SHADER_PARAMS)
{
log_error(Source.name, "invalid param binding: " + std::to_string(conf.location));
}
else if (conf.offset > 3)
{
log_error(Source.name, "invalid offset: " + std::to_string(conf.offset));
}
else if (conf.offset + conf.size > 4)
{
log_error(Source.name, "invalid size: " + std::to_string(conf.size));
}
else
{
Source.param_conf.try_emplace(args[0], conf);
}
}
}
std::size_t count_lines(std::string const &Text, std::size_t const End)
{
return static_cast<std::size_t>(std::count(Text.begin(), Text.begin() + std::min(End, Text.size()), '\n'));
}
// the number of the file in the list of the files of the source, added when it isn't there
std::size_t file_number(shader_source &Source, std::string const &File)
{
auto const lookup{std::find(Source.files.begin(), Source.files.end(), File)};
if (lookup != Source.files.end())
{
return static_cast<std::size_t>(lookup - Source.files.begin());
}
Source.files.emplace_back(File);
return Source.files.size() - 1;
}
// replaces the includes of Text (the content of the file number Number) with the content of the files they name, with #line
// directives which keep the numbers of the lines of each file in the messages of the compiler. an include is a line which
// starts with #include (blanks before it allowed)
void expand_includes(std::string &Text, std::size_t const Number, std::string const &Basedir, shader_source &Source, int const Depth)
{
if (Depth > 32)
{
throw shader_exception("includes nested too deep in " + Source.name);
}
std::string result;
result.reserve(Text.size());
std::size_t linestart{0};
std::size_t linenumber{1};
while (linestart < Text.size())
{
auto lineend{Text.find('\n', linestart)};
if (lineend == std::string::npos)
{
lineend = Text.size();
}
std::string_view const line{Text.data() + linestart, lineend - linestart};
auto position{line.find_first_not_of(" \t")};
bool included{false};
if ((position != std::string_view::npos) && (line.compare(position, 8, "#include") == 0))
{
position = line.find_first_not_of(" \t", position + 8);
if (position != std::string_view::npos)
{
auto const open{line[position]};
auto const close{(open == '<') ? '>' : (open == '"') ? '"' : '\0'};
auto const end{(close != '\0') ? line.find(close, position + 1) : std::string_view::npos};
if (end != std::string_view::npos)
{
std::string filename{line.substr(position + 1, end - position - 1)};
if (filename == "common")
{
// the name the shaders used for the block of the game before it became a file
filename = "common.glsl";
}
auto const number{file_number(Source, filename)};
auto content{read_file(Basedir + filename)};
expand_includes(content, number, Basedir, Source, Depth + 1);
result += "#line 1 " + std::to_string(number) + "\n";
result += content;
if ((false == content.empty()) && (content.back() != '\n'))
{
result += '\n';
}
result += "#line " + std::to_string(linenumber + 1) + " " + std::to_string(Number);
included = true;
}
}
}
if (false == included)
{
result.append(line);
}
if (lineend < Text.size())
{
result += '\n';
}
linestart = lineend + 1;
++linenumber;
}
Text = std::move(result);
}
std::mutex manifest_mutex;
// the manifest of the binaries of a directory: binary -> (source file, hash)
struct manifest_data
{
std::filesystem::file_time_type time{};
bool found{false};
std::unordered_map<std::string, std::vector<std::pair<std::string, std::uint64_t>>> entries;
};
std::unordered_map<std::string, manifest_data> manifests;
manifest_data const &manifest_of(std::string const &Directory)
{
auto &manifest{manifests[Directory]};
auto const path{Directory + "manifest.txt"};
std::error_code error;
auto const time{std::filesystem::last_write_time(path, error)};
if (error)
{
manifest = manifest_data{};
return manifest;
}
if (manifest.found && (manifest.time == time))
{
return manifest;
}
manifest = manifest_data{};
manifest.found = true;
manifest.time = time;
std::ifstream file(path);
std::string line;
while (std::getline(file, line))
{
if (line.empty() || (line[0] == '#'))
{
continue;
}
std::istringstream stream(line);
std::string binary, source, hash;
if (stream >> binary >> source >> hash)
{
try
{
manifest.entries[binary].emplace_back(source, std::stoull(hash, nullptr, 16));
}
catch (std::exception const &)
{
}
}
}
return manifest;
}
} // namespace
char const *shader_api_name(shader_api const Api)
{
return (Api == shader_api::vulkan14 ? "vulkan 1.4" : "opengl 4.6");
}
std::string shader_source_dir(shader_api const Api)
{
return (Api == shader_api::vulkan14 ? "shaders/vk14/" : "shaders/ogl46/");
}
std::string shader_binary_dir(shader_api const Api)
{
return (Api == shader_api::vulkan14 ? "shaders/vk14bin/" : "shaders/ogl46bin/");
}
shader_stage shader_stage_of(std::string const &Name)
{
if (ends_with(Name, ".vert"))
{
return shader_stage::vertex;
}
if (ends_with(Name, ".frag"))
{
return shader_stage::fragment;
}
if (ends_with(Name, ".geom"))
{
return shader_stage::geometry;
}
if (ends_with(Name, ".comp"))
{
return shader_stage::compute;
}
throw shader_exception("unknown shader " + Name);
}
char const *shader_stage_define(shader_stage const Stage)
{
switch (Stage)
{
case shader_stage::vertex:
return "STAGE_VERTEX";
case shader_stage::fragment:
return "STAGE_FRAGMENT";
case shader_stage::geometry:
return "STAGE_GEOMETRY";
case shader_stage::compute:
return "STAGE_COMPUTE";
}
return "STAGE_VERTEX";
}
std::string shader_binary_name(std::string const &Name, std::string const &Variant)
{
return Name + (Variant.empty() ? "" : "." + Variant) + ".spv";
}
std::string shader_variant_of(std::string const &Defines, bool &Plain)
{
Plain = true;
std::string result;
std::istringstream stream(Defines);
std::string line;
while (std::getline(stream, line))
{
std::istringstream linestream(line);
std::string directive, name, value, rest;
if (false == static_cast<bool>(linestream >> directive))
{
continue;
}
if ((directive != "#define") || (false == static_cast<bool>(linestream >> name)))
{
Plain = false;
continue;
}
linestream >> value;
if ((false == value.empty()) && (value != "1"))
{
Plain = false;
}
if (linestream >> rest)
{
Plain = false;
}
result += (result.empty() ? "" : ".") + name;
}
return result;
}
std::uint64_t shader_file_hash(std::string_view const Text)
{
std::uint64_t hash{14695981039346656037ull};
for (std::size_t i = 0; i < Text.size(); ++i)
{
auto const c{Text[i]};
if ((c == '\r') && (i + 1 < Text.size()) && (Text[i + 1] == '\n'))
{
continue;
}
hash ^= static_cast<std::uint8_t>(c);
hash *= 1099511628211ull;
}
return hash;
}
shader_source preprocess_shader(shader_api const Api, std::string const &Name, std::string const &Defines, std::string const &Preamble, bool const Allowmissing, std::string const &Basedir)
{
shader_source source;
source.api = Api;
source.name = Name;
source.stage = shader_stage_of(Name);
bool plain{true};
source.variant = shader_variant_of(Defines, plain);
source.has_binary_name = plain;
auto const basedir{Basedir.empty() ? shader_source_dir(Api) : Basedir};
bool found{false};
auto text{read_text(basedir + Name, found)};
if ((false == found) || text.empty())
{
if (Allowmissing)
{
source.files.emplace_back(Name);
return source;
}
throw shader_exception("cannot read shader: " + basedir + Name);
}
source.has_source = true;
source.files.emplace_back(Name);
// what goes after #version: the variant, the renderer, the stage where the file doesn't say it
std::string preamble{Defines};
if ((false == preamble.empty()) && (preamble.back() != '\n'))
{
preamble += '\n';
}
preamble += Preamble;
if ((false == preamble.empty()) && (preamble.back() != '\n'))
{
preamble += '\n';
}
if (text.find(std::string("#define ") + shader_stage_define(source.stage)) == std::string::npos)
{
preamble += std::string("#define ") + shader_stage_define(source.stage) + " 1\n";
}
// the #version line, which has to stay first
std::size_t versionend{std::string::npos};
{
auto version{text.find("#version")};
if ((version != std::string::npos) && ((version == 0) || (text[version - 1] == '\n')))
{
versionend = text.find('\n', version);
}
}
expand_includes(text, 0, basedir, source, 0);
parse_texture_entries(text, source);
parse_param_entries(text, source);
if (versionend != std::string::npos)
{
// the includes come after the #version line, which is where it was
versionend = text.find('\n', text.find("#version"));
auto const line{count_lines(text, versionend) + 2};
text.insert(versionend + 1, preamble + "#line " + std::to_string(line) + " 0\n");
}
else
{
text = "#version 460 core\n" + preamble + "#line 1 0\n" + text;
}
source.text = std::move(text);
return source;
}
std::vector<std::uint32_t> load_shader_binary(shader_source const &Source, std::string &Reason)
{
Reason.clear();
if (false == Source.has_binary_name)
{
Reason = "the variant isn't one of plain defines";
return {};
}
auto const directory{shader_binary_dir(Source.api)};
auto const name{shader_binary_name(Source.name, Source.variant)};
bool found{false};
auto const data{read_text(directory + name, found)};
if (false == found)
{
return {};
}
if ((data.size() < 20) || (data.size() % 4 != 0))
{
Reason = name + " isn't SPIR-V";
return {};
}
std::vector<std::uint32_t> words(data.size() / 4);
std::memcpy(words.data(), data.data(), data.size());
if (words[0] != 0x07230203u)
{
Reason = name + " isn't SPIR-V";
return {};
}
// the sources the binary was compiled from, as the manifest has them: a binary older than its sources isn't taken
std::lock_guard<std::mutex> lock(manifest_mutex);
auto const &manifest{manifest_of(directory)};
auto const entry{manifest.entries.find(name)};
if (entry != manifest.entries.end())
{
auto const sourcedir{shader_source_dir(Source.api)};
for (auto const &[file, hash] : entry->second)
{
bool sourcefound{false};
auto const text{read_text(sourcedir + file, sourcefound)};
if (false == sourcefound)
{
// the sources aren't there, so they can't be newer
continue;
}
if (shader_file_hash(text) != hash)
{
Reason = name + " was compiled from another " + file + " than the one in " + sourcedir;
return {};
}
}
}
return words;
}
std::vector<std::uint32_t> spirv_specialization_ids(std::vector<std::uint32_t> const &Words)
{
std::vector<std::uint32_t> result;
// the header is 5 words; each instruction starts with its word count (high 16 bits) and opcode (low 16 bits)
std::size_t index{5};
while (index < Words.size())
{
auto const count{Words[index] >> 16};
auto const opcode{Words[index] & 0xffffu};
if ((count == 0) || (index + count > Words.size()))
{
break;
}
// OpDecorate %target SpecId <id>
if ((opcode == 71) && (count >= 4) && (Words[index + 2] == 1))
{
result.push_back(Words[index + 3]);
}
index += count;
}
return result;
}
std::unordered_map<std::string, std::string> shader_file_defines(shader_api const Api, std::string const &Name, std::string const &Basedir)
{
std::unordered_map<std::string, std::string> result;
bool found{false};
auto const text{read_text((Basedir.empty() ? shader_source_dir(Api) : Basedir) + Name, found)};
std::istringstream stream(text);
std::string line;
while (std::getline(stream, line))
{
std::istringstream linestream(line);
std::string directive, name, value;
if ((linestream >> directive >> name) && (directive == "#define"))
{
std::getline(linestream, value);
auto const first{value.find_first_not_of(" \t")};
auto const last{value.find_last_not_of(" \t\r")};
result[name] = (first == std::string::npos ? std::string{} : value.substr(first, last - first + 1));
}
}
return result;
}
bool shader_constants_match(shader_api const Api, std::string const &Name, std::string const &Preamble, std::string const &Basedir, std::vector<std::string> &Differences)
{
Differences.clear();
auto const file{shader_file_defines(Api, Name, Basedir)};
if (file.empty())
{
Differences.emplace_back(Name + " can't be read");
return false;
}
// the same number however it's written: 0.2 and 0.200000, 3 and 3U
auto const normalized = [](std::string Value) {
if ((false == Value.empty()) && ((Value.back() == 'U') || (Value.back() == 'u')))
{
Value.pop_back();
}
try
{
std::size_t used{0};
auto const number{std::stod(Value, &used)};
if (used == Value.size())
{
return std::to_string(number);
}
}
catch (std::exception const &)
{
}
return Value;
};
std::istringstream stream(Preamble);
std::string line;
while (std::getline(stream, line))
{
std::istringstream linestream(line);
std::string directive, name, value;
if (!(linestream >> directive >> name >> value) || (directive != "#define"))
{
continue;
}
auto const lookup{file.find(name)};
if (lookup == file.end())
{
Differences.emplace_back(name + " is missing");
}
else if (normalized(lookup->second) != normalized(value))
{
Differences.emplace_back(name + " is " + lookup->second + " in the file and " + value + " in the game");
}
}
return Differences.empty();
}
} // namespace gfx

View File

@@ -0,0 +1,173 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the shader files of the renderers, whichever api compiles them: where they are, how a file is made ready to compile (its
// includes, the directives of the materials, what the renderer puts before the source), and the SPIR-V binaries compiled from
// them ahead of time, which the renderers load before they compile the source.
//
// each api has a directory of sources and one of binaries: shaders/ogl46/ and shaders/ogl46bin/ for opengl 4.6,
// shaders/vk14/ and shaders/vk14bin/ for vulkan 1.4. a binary is named after its source, <file>.spv, and a variant of the
// shader (the defines of the renderer, e.g. MODEL_DRAWS) <file>.<DEFINE>.spv. the sources can be compiled by glslc as they are
// (shaders/compile_spirv.py), which also writes the manifest of the binaries: the sources each binary was compiled from, so a
// binary older than its sources isn't taken.
//
// no graphics api here: the file is also built into the shader validator (ci_shadervalidator)
#pragma once
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
namespace gfx
{
class shader_exception : public std::runtime_error
{
using runtime_error::runtime_error;
};
enum class shader_api
{
opengl46,
vulkan14
};
enum class shader_stage
{
vertex,
fragment,
geometry,
compute
};
// textures and parameters a material shader can take, the same as gl::MAX_TEXTURES and gl::MAX_PARAMS
constexpr std::size_t MAX_SHADER_TEXTURES = 8;
constexpr std::size_t MAX_SHADER_PARAMS = 3;
// what the texture of a unit holds, as #pragma texture(name, unit, components) gives it. the values are those of opengl
// (GL_RED ... GL_SRGB_ALPHA), which the textures take as the hint of their format
enum class shader_components : int
{
R = 0x1903,
RG = 0x8227,
RGB = 0x1907,
RGBA = 0x1908,
sRGB = 0x8C40,
sRGB_A = 0x8C42
};
struct shader_texture_entry
{
std::size_t id;
shader_components components;
};
enum class shader_param_default
{
required,
nan,
zero,
one,
ambient,
diffuse,
specular,
glossiness
};
// #pragma param(name, location, offset, size, default): a part of one of the vec4 parameters of the model block
struct shader_param_entry
{
std::size_t location;
std::size_t offset;
std::size_t size;
shader_param_default defaultparam;
};
using shader_texture_conf = std::unordered_map<std::string, shader_texture_entry>;
using shader_param_conf = std::unordered_map<std::string, shader_param_entry>;
// a program of a material as the materials see it, whichever api draws it: the textures and the parameters it takes, the
// name it was fetched by (gfx_renderer::Fetch_Shader)
class shader_program
{
public:
virtual ~shader_program() = default;
shader_texture_conf texture_conf;
shader_param_conf param_conf;
std::string name;
};
// a shader file made ready to compile
struct shader_source
{
shader_api api{shader_api::opengl46};
shader_stage stage{shader_stage::vertex};
// the file as asked for, e.g. mat_default.frag
std::string name;
// the names of the defines of the variant joined with dots (MODEL_DRAWS), empty for the shader itself. a variant whose
// defines aren't all plain flags (#define NAME or #define NAME 1) has no binary: invalid_variant
std::string variant;
bool has_binary_name{true};
// whether the source file is there; a binary can be loaded without it, but the material directives come from the source
bool has_source{false};
// the source with the preamble put after #version and the includes expanded, for a compiler of the source. the files are
// told apart in the messages of the compiler by their numbers in files (#line <line> <number>)
std::string text;
// the file and its includes, relative to the directory of the sources, by their numbers
std::vector<std::string> files;
shader_texture_conf texture_conf;
shader_param_conf param_conf;
};
// "opengl 4.6", "vulkan 1.4"
char const *shader_api_name(shader_api Api);
// the directories of the sources and of the binaries of an api: shaders/ogl46/, shaders/ogl46bin/ ...
std::string shader_source_dir(shader_api Api);
std::string shader_binary_dir(shader_api Api);
// the stage of a file by its extension (.vert, .frag, .geom, .comp). throws shader_exception for another one
shader_stage shader_stage_of(std::string const &Name);
// the define the files set for their stage: STAGE_VERTEX ...
char const *shader_stage_define(shader_stage Stage);
// the file name of the binary of a shader: mat_default.frag.spv, vertex.vert.MODEL_DRAWS.spv
std::string shader_binary_name(std::string const &Name, std::string const &Variant);
// the variant named by the defines of a renderer ("#define MODEL_DRAWS 1\n" -> MODEL_DRAWS). Plain: false when a define isn't
// a plain flag (the variant can't have a binary then)
std::string shader_variant_of(std::string const &Defines, bool &Plain);
// the hash the manifest of the binaries keeps of a source file: fnv-1a 64 of the text with the line ends made \n
std::uint64_t shader_file_hash(std::string_view Text);
// reads the shader Name of Api and makes it ready to compile: the includes expanded (#include "file", #include <file>; <common>
// stands for common.glsl), #pragma texture(...) and #pragma param(...) read and taken out of the text, and after the #version
// line (#version 460 core put first when the file has none) the defines of the variant, the preamble of the renderer and,
// where the file doesn't set it, the define of its stage. Basedir: the directory of the sources, shader_source_dir() when empty.
// a missing file is an error unless Allowmissing (the source then comes back with has_source false). throws shader_exception
shader_source preprocess_shader(shader_api Api, std::string const &Name, std::string const &Defines, std::string const &Preamble, bool const Allowmissing = false,
std::string const &Basedir = {});
// the SPIR-V binary of a shader from the directory of the binaries of its api. empty when there's none, when the manifest of the
// binaries says the binary was compiled from other sources than those there now, or when the file isn't SPIR-V; Reason says
// which (empty when there's simply no binary)
std::vector<std::uint32_t> load_shader_binary(shader_source const &Source, std::string &Reason);
// the ids of the specialization constants of a SPIR-V module (OpDecorate ... SpecId)
std::vector<std::uint32_t> spirv_specialization_ids(std::vector<std::uint32_t> const &Words);
// the #define lines of a source, NAME -> value as written, for the checks of the constants the binaries were compiled with
std::unordered_map<std::string, std::string> shader_file_defines(shader_api Api, std::string const &Name, std::string const &Basedir = {});
// whether the constants a renderer puts before the sources (the #define lines of Preamble) are those the file Name of Api writes
// for itself (the binaries are compiled by glslc with the values of the file). Differences: what isn't the same
bool shader_constants_match(shader_api Api, std::string const &Name, std::string const &Preamble, std::string const &Basedir, std::vector<std::string> &Differences);
} // namespace gfx

View File

@@ -107,7 +107,7 @@ public:
IMaterial const *
Material( material_handle const Material ) const override { return m_materials.at(Material - 1).get(); }
// shader methods
auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gl::program> override { throw std::runtime_error("not impl"); }
auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gfx::shader_program> override { throw std::runtime_error("not impl"); }
// texture methods
texture_handle
Fetch_Texture( std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA ) override { throw std::runtime_error("not impl"); }

View File

@@ -95,19 +95,19 @@ bool opengl46_grass::init()
return false;
}
m_vao = std::make_unique<gl::vao>();
auto const location = [&](char const *Name) { return ::glGetUniformLocation(*m_program, Name); };
m_uniforms.tile = location("grass_tile");
m_uniforms.part = location("grass_part");
m_uniforms.chunk = location("grass_chunk");
m_uniforms.camera = location("grass_camera");
m_uniforms.lod = location("grass_lod");
m_uniforms.wind = location("grass_wind");
m_uniforms.base = location("grass_base");
m_uniforms.tip = location("grass_tip");
m_uniforms.shape = location("grass_shape");
m_uniforms.share = location("grass_share");
m_uniforms.dry = location("grass_dry");
m_uniforms.season = location("grass_season");
// the locations grass.vert gives its uniforms (a program loaded from SPIR-V doesn't know their names)
m_uniforms.tile = 0;
m_uniforms.part = 1;
m_uniforms.chunk = 2;
m_uniforms.camera = 3;
m_uniforms.lod = 4;
m_uniforms.wind = 5;
m_uniforms.base = 6;
m_uniforms.tip = 10;
m_uniforms.shape = 14;
m_uniforms.share = 18;
m_uniforms.dry = 19;
m_uniforms.season = 23;
m_state = 1;
return true;
}

View File

@@ -200,8 +200,8 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
return false;
}
scene_ubo = std::make_unique<gl::ubo>(sizeof(gl::scene_ubs), 0);
model_ubo = std::make_unique<gl::stream_ubo>(sizeof(gl::model_ubs), 1, Global.gfx_bufferstreaming);
scene_ubo = std::make_unique<gl::ubo>(sizeof(gl::scene_ubs), gl::SCENE_UBO_BINDING);
model_ubo = std::make_unique<gl::stream_ubo>(sizeof(gl::model_ubs), gl::MODEL_UBO_BINDING, Global.gfx_bufferstreaming);
light_ssbo = std::make_unique<gl::ssbo>(sizeof(gl::light_data), gl::LIGHT_SSBO_BINDING);
light_ssbo->bind_base();
{
@@ -673,6 +673,8 @@ std::unique_ptr<gl::program> opengl46_renderer::make_shader(std::string v, std::
bool opengl46_renderer::Render()
{
// the texture names bound before may have been given to other textures since the last frame
opengl_texture::reset_unit_cache();
Timer::subsystem.gfx_total.start();
std::optional<int64_t> result = m_timequery->result();
@@ -3270,7 +3272,7 @@ bool opengl46_renderer::Blendable_Material(material_handle const Material)
return m_materials.blendable(Material);
}
std::string opengl46_renderer::Shader_Name(std::shared_ptr<gl::program> const &Program) const
std::string opengl46_renderer::Shader_Name(std::shared_ptr<gfx::shader_program> const &Program) const
{
for (auto const &shader : m_shaders)
if (shader.second == Program)
@@ -3278,7 +3280,7 @@ std::string opengl46_renderer::Shader_Name(std::shared_ptr<gl::program> const &P
return {};
}
std::shared_ptr<gl::program> opengl46_renderer::Fetch_Shader(const std::string &name)
std::shared_ptr<gfx::shader_program> opengl46_renderer::Fetch_Shader(const std::string &name)
{
auto it = m_shaders.find(name);
if (it == m_shaders.end())
@@ -3472,7 +3474,7 @@ void opengl46_renderer::Bind_Material( material_handle const Material, TSubModel
if (lastdiff)
glBindTextures(0, lastdiff, &opengl_texture::units[0]);
auto *program{material.shader.get()};
auto *program{static_cast<gl::program *>(material.shader.get())};
if (m_bindmultidraw)
{
// the replay of the instances drawn from the gpu culling with multi draws (Render_Instances_Indirect_Colour())
@@ -4887,7 +4889,7 @@ int opengl46_renderer::Prepass_Class( material_handle const Material )
if( Material == null_handle ) { return 0; }
auto const &material { m_materials.material( Material ) };
if( ( false == material.is_good ) || ( material.shader == nullptr ) ) { return 0; }
auto const lookup { m_prepassclasses.find( material.shader.get() ) };
auto const lookup { m_prepassclasses.find( static_cast<gl::program const *>( material.shader.get() ) ) };
if( lookup != m_prepassclasses.end() ) { return lookup->second; }
// names the programs are fetched by: mat_<name>.frag
@@ -4904,7 +4906,7 @@ int opengl46_renderer::Prepass_Class( material_handle const Material )
prepassclass = ( alphatested.count( shader.first ) > 0 ? 2 : solid.count( shader.first ) > 0 ? 1 : 0 );
break;
}
m_prepassclasses.emplace( material.shader.get(), prepassclass );
m_prepassclasses.emplace( static_cast<gl::program const *>( material.shader.get() ), prepassclass );
return prepassclass;
}
@@ -5678,7 +5680,7 @@ gl::program const *opengl46_renderer::Indirect_Program( material_handle Material
material = &m_materials.material( m_invalid_material );
if( false == material->is_good ) { return nullptr; }
}
return material->shader.get();
return static_cast<gl::program const *>( material->shader.get() );
}
gl::program *opengl46_renderer::Multidraw_Program( gl::program const *Program )
@@ -8867,7 +8869,11 @@ std::string const &opengl46_renderer::info_stats() const
void opengl46_renderer::MakeScreenshot()
{
screenshot_manager::make_screenshot();
// rows are tightly packed RGB: the renderer sets GL_PACK_ALIGNMENT to 1
auto img = new char[Global.fb_size.x * Global.fb_size.y * 3];
glReadPixels(0, 0, Global.fb_size.x, Global.fb_size.y, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid*)img);
//m7t: use pbo
screenshot_manager::save(img, Global.fb_size.x, Global.fb_size.y);
}
// model previews
@@ -10239,9 +10245,14 @@ void opengl46_renderer::opengl46_imgui_renderer::Render()
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}
std::uint64_t opengl46_renderer::opengl46_imgui_renderer::Create_Image(std::uint8_t const *Rgba, int const Width, int const Height)
std::uint64_t opengl46_renderer::opengl46_imgui_renderer::Create_Image(std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
{
// the ui colours are made linear in its shaders, the image is too, by sampling it as srgb. the bindings the renderer
return Update_Image(0, Rgba, Width, Height, Options);
}
std::uint64_t opengl46_renderer::opengl46_imgui_renderer::Update_Image(std::uint64_t const Image, std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
{
// the ui colours are made linear in its shaders, an srgb image is too, by sampling it as srgb. the bindings the renderer
// keeps track of are put back as they were
GLint texture{0}, unpack{0}, alignment{4};
glGetIntegerv(GL_TEXTURE_BINDING_2D, &texture);
@@ -10249,14 +10260,18 @@ std::uint64_t opengl46_renderer::opengl46_imgui_renderer::Create_Image(std::uint
glGetIntegerv(GL_UNPACK_ALIGNMENT, &alignment);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
GLuint image{0};
glGenTextures(1, &image);
auto image{static_cast<GLuint>(Image)};
if (image == 0)
glGenTextures(1, &image);
glBindTexture(GL_TEXTURE_2D, image);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
auto const filter{Options.nearest ? GL_NEAREST : GL_LINEAR};
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, Options.mipmaps ? (Options.nearest ? GL_NEAREST_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR) : filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Rgba);
glTexImage2D(GL_TEXTURE_2D, 0, Options.srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Rgba);
if (Options.mipmaps)
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, static_cast<GLuint>(texture));
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast<GLuint>(unpack));
glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);

View File

@@ -117,8 +117,8 @@ class opengl46_renderer : public gfx_renderer {
void
Terrain_Grass( std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks ) override;
// shader methods
auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gl::program> override;
auto Shader_Name( std::shared_ptr<gl::program> const &Program ) const -> std::string override;
auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gfx::shader_program> override;
auto Shader_Name( std::shared_ptr<gfx::shader_program> const &Program ) const -> std::string override;
// texture methods
texture_handle
Fetch_Texture( std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA ) override;
@@ -1178,7 +1178,9 @@ class opengl46_renderer : public gfx_renderer {
virtual void Shutdown() override;
virtual void BeginFrame() override;
virtual void Render() override;
virtual std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height) override;
using imgui_renderer::Create_Image;
virtual std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options) override;
virtual std::uint64_t Update_Image(std::uint64_t const Image, std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options) override;
virtual void Release_Image(std::uint64_t const Image) override;
} m_imgui_renderer;

View File

@@ -92,8 +92,8 @@ class opengl46_terrain_materials
std::vector<slot> m_slots;
std::vector<std::size_t> m_free;
std::unordered_map<material_handle, std::size_t> m_slotof;
std::shared_ptr<gl::program> m_program; // of the chunks, mat_terrain
std::shared_ptr<gl::program> m_holesprogram; // of the chunks with holes, mat_terrain_holes
std::shared_ptr<gfx::shader_program> m_program; // of the chunks, mat_terrain
std::shared_ptr<gfx::shader_program> m_holesprogram; // of the chunks with holes, mat_terrain_holes
std::unique_ptr<gl::postfx> m_copy;
std::unique_ptr<gl::framebuffer> m_framebuffer;
};

View File

@@ -19,9 +19,9 @@ http://mozilla.org/MPL/2.0/.
struct lighting_data;
namespace gl
namespace gfx
{
class program;
class shader_program;
}
namespace gfx
@@ -132,9 +132,9 @@ public:
// again. chunks left out are dropped. backends which can't draw grass ignore it
virtual void Terrain_Grass( std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks ) {}
// shader methods
virtual auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gl::program> = 0;
virtual auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gfx::shader_program> = 0;
// the name the program was fetched by, empty where it wasn't fetched by Fetch_Shader()
virtual auto Shader_Name( std::shared_ptr<gl::program> const &Program ) const -> std::string { return {}; }
virtual auto Shader_Name( std::shared_ptr<gfx::shader_program> const &Program ) const -> std::string { return {}; }
// texture methods
virtual auto Fetch_Texture( std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA ) -> texture_handle = 0;
virtual void Bind_Texture( texture_handle const Texture ) = 0;
@@ -228,16 +228,34 @@ private:
class imgui_renderer
{
public:
// how an image of the user interface is sampled
struct image_options
{
bool srgb{true}; // the colours are srgb, made linear by the sampling; plain values otherwise
bool nearest{false}; // the nearest texel instead of the linear filtering
bool mipmaps{false}; // mipmaps made of the image, for an image drawn smaller than it is
};
virtual bool Init() = 0;
virtual void Shutdown() = 0;
virtual void BeginFrame() = 0;
virtual void Render() = 0;
// images of the user interface itself (e.g. the previews of the node bank), apart from the textures of the scene: rgba, rows
// from the top, in srgb. returns: id for ImGui::Image, 0 if the renderer can't make them
virtual std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height)
// from the top. returns: id for ImGui::Image, 0 if the renderer can't make them
virtual std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
{
return 0;
}
std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height)
{
return Create_Image(Rgba, Width, Height, image_options{});
}
// the image given other content, of the same or another size (Image 0: a new one). returns: its id, which can be a new one
virtual std::uint64_t Update_Image(std::uint64_t const Image, std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
{
Release_Image(Image);
return Create_Image(Rgba, Width, Height, Options);
}
virtual void Release_Image(std::uint64_t const Image) {}
};

View File

@@ -40,14 +40,9 @@ void screenshot_manager::screenshot_save_thread( char *img, int w, int h )
delete[] img;
}
void screenshot_manager::make_screenshot()
void screenshot_manager::save(char *Image, int Width, int Height)
{
// rows are tightly packed RGB: the renderer sets GL_PACK_ALIGNMENT to 1
auto img = new char[Global.fb_size.x * Global.fb_size.y * 3];
glReadPixels(0, 0, Global.fb_size.x, Global.fb_size.y, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid*)img);
//m7t: use pbo
std::thread t(screenshot_save_thread, img, Global.fb_size.x, Global.fb_size.y);
std::thread t(screenshot_save_thread, Image, Width, Height);
t.detach();
}

View File

@@ -3,5 +3,7 @@ class screenshot_manager
static void screenshot_save_thread(char *img , int w, int h);
public:
static void make_screenshot();
// writes an image of the window to a png file in the directory of the screenshots, on a thread of its own. Image: rgb rows,
// tightly packed, the bottom one first (taken over and deleted by the thread)
static void save(char *Image, int Width, int Height);
};

View File

@@ -0,0 +1,54 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the bindings of the shaders of the vulkan renderer, in one place for the c++ code and the shaders: the renderer puts them
// before the sources of shaders/vk14 as #define lines (vk14::binding_preamble()), and shaders/vk14/common.glsl writes the
// same values for the SPIR-V binaries, which glslc compiles without the renderer (the renderer checks they are the same).
//
// the descriptor sets (decision K6 of the plan): set 0 holds the resources of a pass, made when the renderer starts and bound
// with a dynamic offset in the ring of the frame; set 1 is pushed with the draws (vkCmdPushDescriptorSet2): the textures of
// the material and what changes between the draws. a layout can have only one set pushed, so the passes without a set of
// their own (the user interface, the compute passes) push set 0
#pragma once
#include <cstdint>
#include <string>
namespace vk14::bindings
{
// the user interface (imgui): one pushed set with the texture, the transform in the push constants
constexpr std::uint32_t UI_SET = 0;
constexpr std::uint32_t UI_TEXTURE = 0;
// the sets of the passes of the scene (from the stage D1 of the plan)
constexpr std::uint32_t PASS_SET = 0;
constexpr std::uint32_t MATERIAL_SET = 1;
// the textures of a material take the bindings of their units (#pragma texture(name, unit, ...)), 0 .. MAX_TEXTURES - 1
constexpr std::uint32_t MATERIAL_TEXTURE_FIRST = 0;
// the specialization constants of the shaders
// 1: the colour written to the window is encoded by the shader (a window without an srgb format, gfx.shadergamma yes)
constexpr std::uint32_t SPEC_OUTPUT_GAMMA = 0;
} // namespace vk14::bindings
namespace vk14
{
// the bindings above as #define lines, with MASZYNA_CONSTANTS, which tells shaders/vk14/common.glsl not to write its own
inline std::string binding_preamble()
{
auto const define = [](char const *Name, std::uint32_t const Value) { return std::string("#define ") + Name + " " + std::to_string(Value) + "\n"; };
return define("MASZYNA_CONSTANTS", 1) + define("UI_SET", bindings::UI_SET) + define("UI_TEXTURE", bindings::UI_TEXTURE) + define("PASS_SET", bindings::PASS_SET) + define("MATERIAL_SET", bindings::MATERIAL_SET) +
define("MATERIAL_TEXTURE_FIRST", bindings::MATERIAL_TEXTURE_FIRST) + define("SPEC_OUTPUT_GAMMA", bindings::SPEC_OUTPUT_GAMMA);
}
} // namespace vk14

View File

@@ -197,6 +197,8 @@ void query_capabilities(VkPhysicalDevice Physical, feature_chain &Queried, capab
Caps.driver_name = properties12.driverName;
Caps.driver_info = properties12.driverInfo;
Caps.timestamp_period = properties.properties.limits.timestampPeriod;
Caps.limits = properties.properties.limits;
std::copy(std::begin(properties.properties.pipelineCacheUUID), std::end(properties.properties.pipelineCacheUUID), Caps.pipeline_cache_uuid.begin());
VkPhysicalDeviceMemoryProperties memory{};
vkGetPhysicalDeviceMemoryProperties(Physical, &memory);
@@ -215,6 +217,17 @@ void query_capabilities(VkPhysicalDevice Physical, feature_chain &Queried, capab
return;
}
// the properties of 1.4, asked for once the driver is known to have them
{
VkPhysicalDeviceVulkan14Properties properties14{};
properties14.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_PROPERTIES;
VkPhysicalDeviceProperties2 properties2{};
properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
properties2.pNext = &properties14;
vkGetPhysicalDeviceProperties2(Physical, &properties2);
Caps.max_push_descriptors = properties14.maxPushDescriptors;
}
// features
Queried.reset();
vkGetPhysicalDeviceFeatures2(Physical, Queried.head());

View File

@@ -17,6 +17,8 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/vulkan14/vulkan14common.h"
#include <array>
namespace vk14
{
@@ -72,6 +74,11 @@ struct capabilities
std::uint32_t device_id{0};
VkDeviceSize device_local_memory{0};
float timestamp_period{0.f}; // nanoseconds per tick
// the limits of the device, the identity of its pipeline cache (VkPipelineCache data of another device or driver is
// thrown away) and the bindings a push descriptor set can have
VkPhysicalDeviceLimits limits{};
std::array<std::uint8_t, VK_UUID_SIZE> pipeline_cache_uuid{};
std::uint32_t max_push_descriptors{0};
// device extensions
bool ext_swapchain{false};

View File

@@ -127,6 +127,43 @@ bool verify_result(VkResult Result, char const *Call, char const *File, int Line
return false;
}
bool debug_utils_enabled{false};
void name_object(VkDevice Device, VkObjectType Type, std::uint64_t Handle, std::string const &Name)
{
if ((false == debug_utils_enabled) || (vkSetDebugUtilsObjectNameEXT == nullptr) || (Device == VK_NULL_HANDLE) || (Handle == 0) || Name.empty())
{
return;
}
VkDebugUtilsObjectNameInfoEXT info{};
info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT;
info.objectType = Type;
info.objectHandle = Handle;
info.pObjectName = Name.c_str();
vkSetDebugUtilsObjectNameEXT(Device, &info);
}
void begin_label(VkCommandBuffer Commands, char const *Name)
{
if ((false == debug_utils_enabled) || (vkCmdBeginDebugUtilsLabelEXT == nullptr))
{
return;
}
VkDebugUtilsLabelEXT label{};
label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
label.pLabelName = Name;
vkCmdBeginDebugUtilsLabelEXT(Commands, &label);
}
void end_label(VkCommandBuffer Commands)
{
if ((false == debug_utils_enabled) || (vkCmdEndDebugUtilsLabelEXT == nullptr))
{
return;
}
vkCmdEndDebugUtilsLabelEXT(Commands);
}
void log_info(std::string const &Message)
{
WriteLog("vulkan: " + Message);

View File

@@ -31,6 +31,26 @@ std::string memory_size_string(VkDeviceSize Bytes);
// logs a failed call and gives back false; true for VK_SUCCESS
bool verify_result(VkResult Result, char const *Call, char const *File, int Line);
// whether VK_EXT_debug_utils is on (set by vk14::instance): the names of the objects and the labels of the passes
extern bool debug_utils_enabled;
// the 64 bit value of a handle, for the names of the objects
template <typename Handle_> std::uint64_t handle_value(Handle_ Handle)
{
if constexpr (std::is_pointer_v<Handle_>)
{
return reinterpret_cast<std::uint64_t>(Handle);
}
else
{
return static_cast<std::uint64_t>(Handle);
}
}
// a name of an object for the messages of the validation layer and for captures; nothing without debug utils
void name_object(VkDevice Device, VkObjectType Type, std::uint64_t Handle, std::string const &Name);
// a label of a part of the commands (a pass), for captures and the messages of the validation layer
void begin_label(VkCommandBuffer Commands, char const *Name);
void end_label(VkCommandBuffer Commands);
// the log of the renderer, with the "vulkan: " prefix
void log_info(std::string const &Message);
void log_warning(std::string const &Message);

View File

@@ -0,0 +1,168 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14descriptors.h"
#include "rendering/vulkan14/vulkan14device.h"
namespace vk14
{
// samplers
bool samplers::create(device const &Device, float Anisotropy, std::string &Reason)
{
auto const handle{Device.handle()};
auto const maxanisotropy{Device.caps().limits.maxSamplerAnisotropy};
auto const make = [&](VkSampler &Sampler, VkFilter Filter, VkSamplerAddressMode Address, bool Mipmaps, float Aniso, bool Compare, char const *Name) {
VkSamplerCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
info.magFilter = Filter;
info.minFilter = Filter;
info.mipmapMode = (Filter == VK_FILTER_LINEAR ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST);
info.addressModeU = Address;
info.addressModeV = Address;
info.addressModeW = Address;
info.maxLod = (Mipmaps ? VK_LOD_CLAMP_NONE : 0.f);
if (Aniso > 1.f)
{
info.anisotropyEnable = VK_TRUE;
info.maxAnisotropy = std::min(Aniso, maxanisotropy);
}
if (Compare)
{
// reverse z: the fragment is lit where it's nearer the light than the map, its depth greater
info.compareEnable = VK_TRUE;
info.compareOp = VK_COMPARE_OP_GREATER_OR_EQUAL;
info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
}
if (!VK14_VERIFY(vkCreateSampler(handle, &info, nullptr, &Sampler)))
{
Sampler = VK_NULL_HANDLE;
Reason = std::string("the sampler ") + Name + " can't be made";
return false;
}
name_object(handle, VK_OBJECT_TYPE_SAMPLER, handle_value(Sampler), Name);
return true;
};
return make(repeat_aniso, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_REPEAT, true, Anisotropy, false, "repeat aniso") && make(clamp_aniso, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, true, Anisotropy, false, "clamp aniso") &&
make(clamp_linear, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, true, 1.f, false, "clamp linear") && make(clamp_nearest, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, false, 1.f, false, "clamp nearest") &&
make(shadow, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, false, 1.f, true, "shadow");
}
void samplers::destroy(VkDevice Device)
{
for (auto *sampler : {&repeat_aniso, &clamp_aniso, &clamp_linear, &clamp_nearest, &shadow})
{
if (*sampler != VK_NULL_HANDLE)
{
vkDestroySampler(Device, *sampler, nullptr);
*sampler = VK_NULL_HANDLE;
}
}
}
// pushed sets
bool push_set::create(VkDevice Device, std::vector<VkDescriptorSetLayoutBinding> const &Bindings, std::uint32_t Set, VkPipelineBindPoint BindPoint, std::size_t Stride, std::string const &Name)
{
m_bindings = Bindings;
m_stride = Stride;
set = Set;
bind_point = BindPoint;
VkDescriptorSetLayoutCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
info.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT;
info.bindingCount = static_cast<std::uint32_t>(Bindings.size());
info.pBindings = Bindings.data();
if (!VK14_VERIFY(vkCreateDescriptorSetLayout(Device, &info, nullptr, &layout)))
{
layout = VK_NULL_HANDLE;
return false;
}
name_object(Device, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, handle_value(layout), Name);
return true;
}
bool push_set::create_template(VkDevice Device, VkPipelineLayout Layout, std::string const &Name)
{
std::vector<VkDescriptorUpdateTemplateEntry> entries;
for (std::size_t i = 0; i < m_bindings.size(); ++i)
{
VkDescriptorUpdateTemplateEntry entry{};
entry.dstBinding = m_bindings[i].binding;
entry.dstArrayElement = 0;
entry.descriptorCount = m_bindings[i].descriptorCount;
entry.descriptorType = m_bindings[i].descriptorType;
entry.offset = i * m_stride;
entry.stride = m_stride;
entries.push_back(entry);
}
VkDescriptorUpdateTemplateCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO;
info.descriptorUpdateEntryCount = static_cast<std::uint32_t>(entries.size());
info.pDescriptorUpdateEntries = entries.data();
info.templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_PUSH_DESCRIPTORS;
info.descriptorSetLayout = layout;
info.pipelineBindPoint = bind_point;
info.pipelineLayout = Layout;
info.set = set;
if (!VK14_VERIFY(vkCreateDescriptorUpdateTemplate(Device, &info, nullptr, &update)))
{
update = VK_NULL_HANDLE;
return false;
}
name_object(Device, VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE, handle_value(update), Name);
return true;
}
void push_set::destroy(VkDevice Device)
{
if (update != VK_NULL_HANDLE)
{
vkDestroyDescriptorUpdateTemplate(Device, update, nullptr);
update = VK_NULL_HANDLE;
}
if (layout != VK_NULL_HANDLE)
{
vkDestroyDescriptorSetLayout(Device, layout, nullptr);
layout = VK_NULL_HANDLE;
}
}
void push_set::push(VkCommandBuffer Commands, VkPipelineLayout Layout, void const *Data) const
{
VkPushDescriptorSetWithTemplateInfo info{};
info.sType = VK_STRUCTURE_TYPE_PUSH_DESCRIPTOR_SET_WITH_TEMPLATE_INFO;
info.descriptorUpdateTemplate = update;
info.layout = Layout;
info.set = set;
info.pData = Data;
vkCmdPushDescriptorSetWithTemplate2(Commands, &info);
}
VkPipelineLayout make_pipeline_layout(VkDevice Device, std::vector<VkDescriptorSetLayout> const &Sets, std::vector<VkPushConstantRange> const &PushConstants, std::string const &Name)
{
VkPipelineLayoutCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
info.setLayoutCount = static_cast<std::uint32_t>(Sets.size());
info.pSetLayouts = Sets.data();
info.pushConstantRangeCount = static_cast<std::uint32_t>(PushConstants.size());
info.pPushConstantRanges = PushConstants.data();
VkPipelineLayout layout{VK_NULL_HANDLE};
if (!VK14_VERIFY(vkCreatePipelineLayout(Device, &info, nullptr, &layout)))
{
return VK_NULL_HANDLE;
}
name_object(Device, VK_OBJECT_TYPE_PIPELINE_LAYOUT, handle_value(layout), Name);
return layout;
}
} // namespace vk14

View File

@@ -0,0 +1,61 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the descriptors of the vulkan renderer without pools: the samplers every pass shares, the layouts of the pushed sets, and the
// templates the descriptors are pushed by with the draws (vkCmdPushDescriptorSetWithTemplate2). no descriptor is allocated or
// updated while a frame is made
#pragma once
#include "rendering/vulkan14/vulkan14common.h"
namespace vk14
{
class device;
// the samplers of the renderer, made once
struct samplers
{
VkSampler repeat_aniso{VK_NULL_HANDLE}; // the textures of the scene: repeated, mipmapped, anisotropic (anisotropicfiltering)
VkSampler clamp_aniso{VK_NULL_HANDLE}; // the same clamped (textures with the s and t traits)
VkSampler clamp_linear{VK_NULL_HANDLE}; // images of the user interface, post-processing
VkSampler clamp_nearest{VK_NULL_HANDLE};
VkSampler shadow{VK_NULL_HANDLE}; // depth compared (shadow maps, reverse z)
bool create(device const &Device, float Anisotropy, std::string &Reason);
void destroy(VkDevice Device);
};
// a layout of a pushed set and its template
struct push_set
{
VkDescriptorSetLayout layout{VK_NULL_HANDLE};
VkDescriptorUpdateTemplate update{VK_NULL_HANDLE};
std::uint32_t set{0};
VkPipelineBindPoint bind_point{VK_PIPELINE_BIND_POINT_GRAPHICS};
// Bindings: one descriptor each, the data of the template is an array of VkDescriptorImageInfo / VkDescriptorBufferInfo in
// the order of the bindings (Stride bytes per binding)
bool create(VkDevice Device, std::vector<VkDescriptorSetLayoutBinding> const &Bindings, std::uint32_t Set, VkPipelineBindPoint BindPoint, std::size_t Stride, std::string const &Name);
// the template needs the pipeline layout the set is in
bool create_template(VkDevice Device, VkPipelineLayout Layout, std::string const &Name);
void destroy(VkDevice Device);
// pushes Data (laid out as create() was told)
void push(VkCommandBuffer Commands, VkPipelineLayout Layout, void const *Data) const;
private:
std::vector<VkDescriptorSetLayoutBinding> m_bindings;
std::size_t m_stride{0};
};
// a pipeline layout of the given sets and push constants
VkPipelineLayout make_pipeline_layout(VkDevice Device, std::vector<VkDescriptorSetLayout> const &Sets, std::vector<VkPushConstantRange> const &PushConstants, std::string const &Name);
} // namespace vk14

View File

@@ -281,6 +281,12 @@ bool device::create(instance const &Instance, std::string &Reason)
{
extensions.push_back("VK_KHR_portability_subset");
}
// the allocator keeps to the budget of the memory heaps where the driver tells it
m_memory_budget = m_capabilities.ext_memory_budget;
if (m_memory_budget)
{
extensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);
}
}
VkDeviceCreateInfo create{};

View File

@@ -71,6 +71,11 @@ class device
{
return m_graphics_queue;
}
// VK_EXT_memory_budget is on (the allocator keeps to the budget of the heaps)
bool memory_budget_enabled() const
{
return m_memory_budget;
}
private:
static queue_families find_queue_families(VkInstance Instance, VkPhysicalDevice Physical, VkSurfaceKHR Surface, std::uint32_t &TimestampBits);
@@ -82,6 +87,7 @@ class device
queue_families m_families;
feature_chain m_queried;
bool m_portability_subset{false};
bool m_memory_budget{false};
VkQueue m_graphics_queue{VK_NULL_HANDLE};
VkQueue m_compute_queue{VK_NULL_HANDLE};

View File

@@ -0,0 +1,341 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14imgui.h"
#include "rendering/vulkan14/vulkan14bindings.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "imgui/imgui.h"
#include <cstring>
namespace vk14
{
bool imgui_backend::create(resources const &Resources, shader_library &Shaders, pipeline_library &Pipelines, frame_ring &Ring, std::string &Reason)
{
m_resources = &Resources;
m_shaders = &Shaders;
m_pipelines = &Pipelines;
m_ring = &Ring;
auto const device{Resources.gpu->handle()};
VkDescriptorSetLayoutBinding texture{};
texture.binding = bindings::UI_TEXTURE;
texture.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
texture.descriptorCount = 1;
texture.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
if (false == m_set.create(device, {texture}, bindings::UI_SET, VK_PIPELINE_BIND_POINT_GRAPHICS, sizeof(VkDescriptorImageInfo), "ui texture"))
{
Reason = "no descriptor set layout for the user interface";
return false;
}
m_layout = make_pipeline_layout(device, {m_set.layout}, {VkPushConstantRange{VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(float) * 4}}, "ui");
if ((m_layout == VK_NULL_HANDLE) || (false == m_set.create_template(device, m_layout, "ui texture")))
{
Reason = "no pipeline layout for the user interface";
return false;
}
try
{
m_vertex = Shaders.get("imgui.vert");
m_fragment = Shaders.get("imgui.frag");
}
catch (gfx::shader_exception const &e)
{
Reason = std::string("the shaders of the user interface: ") + e.what();
return false;
}
return true;
}
void imgui_backend::destroy()
{
if (m_resources == nullptr)
{
return;
}
auto const device{m_resources->gpu->handle()};
for (auto const image : m_images)
{
m_resources->images->remove(image, *m_resources->deletions, m_resources->uploads->current_value());
}
m_images.clear();
m_font = 0;
if (ImGui::GetCurrentContext() != nullptr)
{
// imgui outlives the renderer (ui_layer::shutdown() comes after it)
auto &io{ImGui::GetIO()};
io.Fonts->SetTexID(0);
io.BackendRendererName = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
}
m_set.destroy(device);
if (m_layout != VK_NULL_HANDLE)
{
vkDestroyPipelineLayout(device, m_layout, nullptr);
m_layout = VK_NULL_HANDLE;
}
m_vertex.reset();
m_fragment.reset();
m_drawdata = nullptr;
m_resources = nullptr;
}
bool imgui_backend::Init()
{
auto &io{ImGui::GetIO()};
io.BackendRendererName = "maszyna vulkan14";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
BeginFrame();
return (m_resources != nullptr);
}
void imgui_backend::Shutdown()
{
auto &io{ImGui::GetIO()};
if ((m_font != 0) && (m_resources != nullptr))
{
Release_Image(m_font);
m_font = 0;
}
io.Fonts->SetTexID(0);
io.BackendRendererName = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
m_drawdata = nullptr;
}
void imgui_backend::BeginFrame()
{
if (m_resources == nullptr)
{
return;
}
// the font, made again when imgui built it anew
auto &io{ImGui::GetIO()};
if ((m_font != 0) && io.Fonts->IsBuilt() && (io.Fonts->TexID == m_font))
{
return;
}
unsigned char *pixels{nullptr};
int width{0}, height{0};
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
image_options options;
// the alpha of the glyphs as it is, as the opengl renderer has it
options.srgb = false;
m_font = Update_Image(m_font, pixels, width, height, options);
io.Fonts->SetTexID(static_cast<ImTextureID>(m_font));
}
void imgui_backend::Render()
{
m_drawdata = ImGui::GetDrawData();
}
std::uint64_t imgui_backend::Create_Image(std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
{
return Update_Image(0, Rgba, Width, Height, Options);
}
std::uint64_t imgui_backend::Update_Image(std::uint64_t const Id, std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
{
if ((m_resources == nullptr) || (Rgba == nullptr) || (Width <= 0) || (Height <= 0))
{
return Id;
}
// only the images made here are given new content; another id (released, or of a texture) gets a new image
auto const *current{(std::find(m_images.begin(), m_images.end(), Id) != m_images.end()) ? m_resources->images->find(Id) : nullptr};
auto const Image{current != nullptr ? Id : 0};
auto const format{Options.srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM};
auto const width{static_cast<std::uint32_t>(Width)}, height{static_cast<std::uint32_t>(Height)};
auto const mipmaps{Options.mipmaps && m_resources->uploads->can_blit(format)};
auto const sampler{Options.nearest ? m_resources->sampling->clamp_nearest : m_resources->sampling->clamp_linear};
std::shared_ptr<image> target;
if (current != nullptr)
{
auto const &desc{current->target->desc()};
if ((desc.extent.width == width) && (desc.extent.height == height) && (desc.format == format) && ((desc.mips > 1) == mipmaps))
{
// the same image given new content; the frames which drew it come first in the queue
target = current->target;
}
}
if (target == nullptr)
{
image_desc desc;
desc.format = format;
desc.extent = {width, height, 1};
desc.mips = (mipmaps ? static_cast<std::uint32_t>(std::floor(std::log2(std::max(width, height)))) + 1 : 1);
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | (mipmaps ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0);
target = std::make_shared<image>();
if (false == target->create(*m_resources->memory, desc, "ui image"))
{
return Id;
}
}
if (false == m_resources->uploads->upload(target, Rgba, static_cast<VkDeviceSize>(width) * height * 4, {{0, {width, height, 1}}}, mipmaps))
{
return Id;
}
if (Image != 0)
{
if (current->target != target)
{
// the old image goes once the frames which drew it are done
m_resources->deletions->push(m_resources->uploads->current_value(), [old = current->target]() {});
}
m_resources->images->replace(Image, target, sampler);
return Image;
}
auto const id{m_resources->images->add(target, sampler)};
m_images.push_back(id);
return id;
}
void imgui_backend::Release_Image(std::uint64_t const Image)
{
if ((m_resources == nullptr) || (Image == 0))
{
return;
}
auto const lookup{std::find(m_images.begin(), m_images.end(), Image)};
if (lookup == m_images.end())
{
// not one of the images of the user interface
return;
}
m_images.erase(lookup);
m_resources->images->remove(Image, *m_resources->deletions, m_resources->uploads->current_value());
}
void imgui_backend::record(VkCommandBuffer Commands, VkExtent2D Extent, VkFormat Format, bool Srgb)
{
auto const *drawdata{m_drawdata};
m_drawdata = nullptr;
if ((drawdata == nullptr) || (drawdata->CmdListsCount == 0) || (drawdata->TotalVtxCount == 0) || (m_resources == nullptr))
{
return;
}
auto const scale{drawdata->FramebufferScale};
if ((drawdata->DisplaySize.x <= 0.f) || (drawdata->DisplaySize.y <= 0.f))
{
return;
}
pipeline_desc desc;
desc.stages = {m_vertex, m_fragment};
desc.layout = m_layout;
desc.vertices.bindings = {VkVertexInputBindingDescription{0, sizeof(ImDrawVert), VK_VERTEX_INPUT_RATE_VERTEX}};
desc.vertices.attributes = {VkVertexInputAttributeDescription{0, 0, VK_FORMAT_R32G32_SFLOAT, static_cast<std::uint32_t>(offsetof(ImDrawVert, pos))},
VkVertexInputAttributeDescription{1, 0, VK_FORMAT_R32G32_SFLOAT, static_cast<std::uint32_t>(offsetof(ImDrawVert, uv))},
VkVertexInputAttributeDescription{2, 0, VK_FORMAT_R8G8B8A8_UNORM, static_cast<std::uint32_t>(offsetof(ImDrawVert, col))}};
desc.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
desc.blending = blend_mode::alpha;
desc.colour_formats = {Format};
// the colour encoded by the shader where the image of the window doesn't do it
desc.specialization = {Srgb ? 0u : 1u};
desc.name = "ui";
auto const pipeline{m_pipelines->get(desc)};
if (pipeline == VK_NULL_HANDLE)
{
return;
}
// the vertices and the indices of the frame, through its ring
auto const vertexbytes{static_cast<VkDeviceSize>(drawdata->TotalVtxCount) * sizeof(ImDrawVert)};
auto const indexbytes{static_cast<VkDeviceSize>(drawdata->TotalIdxCount) * sizeof(ImDrawIdx)};
auto const vertices{m_ring->allocate(vertexbytes, sizeof(ImDrawVert))};
auto const indices{m_ring->allocate(indexbytes, 4)};
if (!vertices || !indices)
{
log_warning("the ring of the frame is too small for the user interface");
return;
}
auto *vertexdata{vertices.data};
auto *indexdata{indices.data};
for (int list = 0; list < drawdata->CmdListsCount; ++list)
{
auto const *commands{drawdata->CmdLists[list]};
std::memcpy(vertexdata, commands->VtxBuffer.Data, commands->VtxBuffer.Size * sizeof(ImDrawVert));
std::memcpy(indexdata, commands->IdxBuffer.Data, commands->IdxBuffer.Size * sizeof(ImDrawIdx));
vertexdata += commands->VtxBuffer.Size * sizeof(ImDrawVert);
indexdata += commands->IdxBuffer.Size * sizeof(ImDrawIdx);
}
vkCmdBindPipeline(Commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
set_draw_state(Commands, Extent);
vkCmdBindVertexBuffers(Commands, 0, 1, &vertices.buffer, &vertices.offset);
vkCmdBindIndexBuffer(Commands, indices.buffer, indices.offset, sizeof(ImDrawIdx) == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32);
// the pixels of the display (top left origin, y down) to the clip space of vulkan (y down too)
float transform[4];
transform[0] = 2.f / drawdata->DisplaySize.x;
transform[1] = 2.f / drawdata->DisplaySize.y;
transform[2] = -1.f - drawdata->DisplayPos.x * transform[0];
transform[3] = -1.f - drawdata->DisplayPos.y * transform[1];
VkPushConstantsInfo push{};
push.sType = VK_STRUCTURE_TYPE_PUSH_CONSTANTS_INFO;
push.layout = m_layout;
push.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
push.offset = 0;
push.size = sizeof(transform);
push.pValues = transform;
vkCmdPushConstants2(Commands, &push);
auto const clipoffset{drawdata->DisplayPos};
std::uint32_t vertexoffset{0}, indexoffset{0};
std::uint64_t pushed{~0ull};
for (int list = 0; list < drawdata->CmdListsCount; ++list)
{
auto const *commands{drawdata->CmdLists[list]};
for (int index = 0; index < commands->CmdBuffer.Size; ++index)
{
auto const &command{commands->CmdBuffer[index]};
if (command.UserCallback != nullptr)
{
// no callbacks in the user interface of the simulator; the one which resets the state is all there is to do
if (command.UserCallback != ImDrawCallback_ResetRenderState)
{
command.UserCallback(commands, &command);
}
continue;
}
// the clip rectangle in the pixels of the image
auto const minx{std::max((command.ClipRect.x - clipoffset.x) * scale.x, 0.f)};
auto const miny{std::max((command.ClipRect.y - clipoffset.y) * scale.y, 0.f)};
auto const maxx{std::min((command.ClipRect.z - clipoffset.x) * scale.x, static_cast<float>(Extent.width))};
auto const maxy{std::min((command.ClipRect.w - clipoffset.y) * scale.y, static_cast<float>(Extent.height))};
if ((maxx <= minx) || (maxy <= miny))
{
continue;
}
auto const *entry{m_resources->images->find(command.GetTexID())};
if (entry == nullptr)
{
// no image of that id (not made, or gone)
continue;
}
VkRect2D scissor{{static_cast<std::int32_t>(minx), static_cast<std::int32_t>(miny)}, {static_cast<std::uint32_t>(maxx - minx), static_cast<std::uint32_t>(maxy - miny)}};
vkCmdSetScissor(Commands, 0, 1, &scissor);
if (pushed != command.GetTexID())
{
VkDescriptorImageInfo info{entry->sampler, entry->target->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
m_set.push(Commands, m_layout, &info);
pushed = command.GetTexID();
}
vkCmdDrawIndexed(Commands, command.ElemCount, 1, command.IdxOffset + indexoffset, static_cast<std::int32_t>(command.VtxOffset + vertexoffset), 0);
}
vertexoffset += static_cast<std::uint32_t>(commands->VtxBuffer.Size);
indexoffset += static_cast<std::uint32_t>(commands->IdxBuffer.Size);
}
}
} // namespace vk14

View File

@@ -0,0 +1,75 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the user interface (imgui) drawn by the vulkan renderer (decision K10 of the plan): a backend of its own instead of
// imgui_impl_vulkan, which would need a pool of descriptors and a set per texture. the texture of a draw is pushed with it
// (push descriptors), the vertices and the indices go through the ring of the frame, and ImTextureID is the id of the image in
// the table of the renderer, the same for the textures (ITexture::get_id()), the images of the user interface (Create_Image())
// and the font. the colours are mixed as the user interface of the opengl renderer mixes them: in linear light, encoded by the
// srgb image of the window or, with gfx.shadergamma, by the shader
#pragma once
#include "rendering/renderer.h"
#include "rendering/vulkan14/vulkan14descriptors.h"
#include "rendering/vulkan14/vulkan14shaders.h"
#include "rendering/vulkan14/vulkan14texture.h"
struct ImDrawData;
namespace vk14
{
class imgui_backend : public imgui_renderer
{
public:
// what the backend draws with, from the renderer
bool create(resources const &Resources, shader_library &Shaders, pipeline_library &Pipelines, frame_ring &Ring, std::string &Reason);
void destroy();
// imgui_renderer
bool Init() override;
void Shutdown() override;
void BeginFrame() override;
void Render() override;
using imgui_renderer::Create_Image;
std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options) override;
std::uint64_t Update_Image(std::uint64_t const Image, std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options) override;
void Release_Image(std::uint64_t const Image) override;
// whether imgui made a frame to draw since the last record()
bool has_frame() const
{
return m_drawdata != nullptr;
}
// records the frame of imgui into Commands, in a rendering to an image of Extent and Format begun by the renderer. Srgb: the
// image encodes the colour itself
void record(VkCommandBuffer Commands, VkExtent2D Extent, VkFormat Format, bool Srgb);
// the frame of imgui is dropped (nothing to draw it to)
void skip()
{
m_drawdata = nullptr;
}
private:
resources const *m_resources{nullptr};
shader_library *m_shaders{nullptr};
pipeline_library *m_pipelines{nullptr};
frame_ring *m_ring{nullptr};
push_set m_set;
VkPipelineLayout m_layout{VK_NULL_HANDLE};
std::shared_ptr<shader_code const> m_vertex;
std::shared_ptr<shader_code const> m_fragment;
std::uint64_t m_font{0};
ImDrawData *m_drawdata{nullptr};
// the images made by Create_Image()
std::vector<std::uint64_t> m_images;
};
} // namespace vk14

View File

@@ -249,6 +249,7 @@ bool instance::collect_extensions(std::vector<char const *> &Extensions, instanc
{
Extensions.clear();
m_debug_utils_enabled = false;
debug_utils_enabled = false;
std::uint32_t glfwcount{0};
auto const **glfwextensions{glfwGetRequiredInstanceExtensions(&glfwcount)};
@@ -281,6 +282,7 @@ bool instance::collect_extensions(std::vector<char const *> &Extensions, instanc
Extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
m_debug_utils_enabled = true;
debug_utils_enabled = true;
}
else
{
@@ -337,6 +339,7 @@ void instance::destroy()
}
m_validation_enabled = false;
m_debug_utils_enabled = false;
debug_utils_enabled = false;
}
} // namespace vk14

View File

@@ -0,0 +1,380 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14memory.h"
#include "rendering/vulkan14/vulkan14instance.h"
#include "rendering/vulkan14/vulkan14device.h"
namespace vk14
{
// allocator
allocator::~allocator()
{
destroy();
}
bool allocator::create(instance const &Instance, device const &Device, std::string &Reason)
{
VmaAllocatorCreateInfo info{};
info.vulkanApiVersion = VK_API_VERSION_1_4;
info.instance = Instance.handle();
info.physicalDevice = Device.physical();
info.device = Device.handle();
info.flags = VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_BIT;
if (Device.memory_budget_enabled())
{
info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
}
// the functions as volk loaded them
VmaVulkanFunctions functions{};
if (!VK14_VERIFY(vmaImportVulkanFunctionsFromVolk(&info, &functions)))
{
Reason = "the functions of the memory allocator can't be loaded";
return false;
}
info.pVulkanFunctions = &functions;
if (!VK14_VERIFY(vmaCreateAllocator(&info, &m_allocator)))
{
m_allocator = VK_NULL_HANDLE;
Reason = "the memory allocator can't be made";
return false;
}
m_device = Device.handle();
VkPhysicalDeviceMemoryProperties memory{};
vkGetPhysicalDeviceMemoryProperties(Device.physical(), &memory);
m_heapcount = memory.memoryHeapCount;
m_deviceheaps.assign(m_heapcount, false);
for (std::uint32_t i = 0; i < m_heapcount; ++i)
{
m_deviceheaps[i] = ((memory.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0);
}
return true;
}
void allocator::destroy()
{
if (m_allocator != VK_NULL_HANDLE)
{
vmaDestroyAllocator(m_allocator);
m_allocator = VK_NULL_HANDLE;
}
m_device = VK_NULL_HANDLE;
}
void allocator::budget(VkDeviceSize &Usage, VkDeviceSize &Budget) const
{
Usage = 0;
Budget = 0;
if (m_allocator == VK_NULL_HANDLE)
{
return;
}
std::vector<VmaBudget> budgets(m_heapcount);
vmaGetHeapBudgets(m_allocator, budgets.data());
for (std::uint32_t i = 0; i < m_heapcount; ++i)
{
if (m_deviceheaps[i])
{
Usage += budgets[i].usage;
Budget += budgets[i].budget;
}
}
}
// buffer
buffer::buffer(buffer &&Other) noexcept
{
*this = std::move(Other);
}
buffer &buffer::operator=(buffer &&Other) noexcept
{
if (this != &Other)
{
destroy();
std::swap(m_allocator, Other.m_allocator);
std::swap(m_buffer, Other.m_buffer);
std::swap(m_allocation, Other.m_allocation);
std::swap(m_size, Other.m_size);
std::swap(m_mapped, Other.m_mapped);
}
return *this;
}
buffer::~buffer()
{
destroy();
}
bool buffer::create(allocator const &Allocator, VkDeviceSize Size, VkBufferUsageFlags Usage, memory_kind Kind, std::string const &Name)
{
destroy();
VkBufferCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
info.size = std::max<VkDeviceSize>(Size, 4);
info.usage = Usage;
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VmaAllocationCreateInfo allocation{};
allocation.usage = VMA_MEMORY_USAGE_AUTO;
switch (Kind)
{
case memory_kind::device:
allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
break;
case memory_kind::upload:
allocation.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
break;
case memory_kind::readback:
allocation.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
break;
}
VmaAllocationInfo allocated{};
if (!VK14_VERIFY(vmaCreateBuffer(Allocator.handle(), &info, &allocation, &m_buffer, &m_allocation, &allocated)))
{
m_buffer = VK_NULL_HANDLE;
m_allocation = VK_NULL_HANDLE;
return false;
}
m_allocator = Allocator.handle();
m_size = Size;
m_mapped = static_cast<std::uint8_t *>(allocated.pMappedData);
name_object(Allocator.device_handle(), VK_OBJECT_TYPE_BUFFER, handle_value(m_buffer), Name);
return true;
}
void buffer::destroy()
{
if (m_buffer != VK_NULL_HANDLE)
{
vmaDestroyBuffer(m_allocator, m_buffer, m_allocation);
}
m_buffer = VK_NULL_HANDLE;
m_allocation = VK_NULL_HANDLE;
m_size = 0;
m_mapped = nullptr;
}
void buffer::flush(VkDeviceSize Offset, VkDeviceSize Size) const
{
if ((m_allocation != VK_NULL_HANDLE) && (Size > 0))
{
// vma does nothing for coherent memory
vmaFlushAllocation(m_allocator, m_allocation, Offset, Size);
}
}
// image
image::~image()
{
destroy();
}
VkImageAspectFlags image::aspect() const
{
switch (m_desc.format)
{
case VK_FORMAT_D16_UNORM:
case VK_FORMAT_D32_SFLOAT:
case VK_FORMAT_X8_D24_UNORM_PACK32:
return VK_IMAGE_ASPECT_DEPTH_BIT;
case VK_FORMAT_D24_UNORM_S8_UINT:
case VK_FORMAT_D32_SFLOAT_S8_UINT:
case VK_FORMAT_D16_UNORM_S8_UINT:
return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
default:
break;
}
return VK_IMAGE_ASPECT_COLOR_BIT;
}
bool image::create(allocator const &Allocator, image_desc const &Desc, std::string const &Name)
{
destroy();
m_desc = Desc;
VkImageCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
info.flags = Desc.flags;
info.imageType = Desc.type;
info.format = Desc.format;
info.extent = Desc.extent;
info.mipLevels = std::max(Desc.mips, 1u);
info.arrayLayers = std::max(Desc.layers, 1u);
info.samples = Desc.samples;
info.tiling = VK_IMAGE_TILING_OPTIMAL;
info.usage = Desc.usage;
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VmaAllocationCreateInfo allocation{};
allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
VmaAllocationInfo allocated{};
if (!VK14_VERIFY(vmaCreateImage(Allocator.handle(), &info, &allocation, &m_image, &m_allocation, &allocated)))
{
m_image = VK_NULL_HANDLE;
m_allocation = VK_NULL_HANDLE;
return false;
}
m_allocator = Allocator.handle();
m_device = Allocator.device_handle();
m_bytes = allocated.size;
VkImageViewCreateInfo viewinfo{};
viewinfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewinfo.image = m_image;
viewinfo.viewType = Desc.view_type;
viewinfo.format = Desc.format;
viewinfo.components = Desc.components;
viewinfo.subresourceRange = {aspect(), 0, info.mipLevels, 0, info.arrayLayers};
if (!VK14_VERIFY(vkCreateImageView(m_device, &viewinfo, nullptr, &m_view)))
{
m_view = VK_NULL_HANDLE;
destroy();
return false;
}
state = image_state{};
name_object(m_device, VK_OBJECT_TYPE_IMAGE, handle_value(m_image), Name);
name_object(m_device, VK_OBJECT_TYPE_IMAGE_VIEW, handle_value(m_view), Name);
return true;
}
void image::destroy()
{
if (m_view != VK_NULL_HANDLE)
{
vkDestroyImageView(m_device, m_view, nullptr);
}
if (m_image != VK_NULL_HANDLE)
{
vmaDestroyImage(m_allocator, m_image, m_allocation);
}
m_view = VK_NULL_HANDLE;
m_image = VK_NULL_HANDLE;
m_allocation = VK_NULL_HANDLE;
m_bytes = 0;
state = image_state{};
}
void transition(VkCommandBuffer Commands, image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access)
{
auto const writes{VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT |
VK_ACCESS_2_HOST_WRITE_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT};
auto const &from{Image.state};
// reads after reads in the same layout need nothing
if ((from.layout == Layout) && ((from.access & writes) == 0) && ((Access & writes) == 0))
{
Image.state.stage |= Stage;
Image.state.access |= Access;
return;
}
VkImageMemoryBarrier2 barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
barrier.srcStageMask = (from.stage != VK_PIPELINE_STAGE_2_NONE ? from.stage : VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT);
barrier.srcAccessMask = (from.access & writes);
barrier.dstStageMask = Stage;
barrier.dstAccessMask = Access;
barrier.oldLayout = from.layout;
barrier.newLayout = Layout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = Image.handle();
barrier.subresourceRange = {Image.aspect(), 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS};
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.imageMemoryBarrierCount = 1;
dependency.pImageMemoryBarriers = &barrier;
vkCmdPipelineBarrier2(Commands, &dependency);
Image.state = image_state{Layout, Stage, Access};
}
// deletion queue
void deletion_queue::push(std::uint64_t Value, std::function<void()> &&Deleter)
{
m_entries.emplace_back(Value, std::move(Deleter));
}
void deletion_queue::collect(std::uint64_t Completed)
{
// the values only grow, so the queue is in their order
while ((false == m_entries.empty()) && (m_entries.front().first <= Completed))
{
auto deleter{std::move(m_entries.front().second)};
m_entries.pop_front();
deleter();
}
}
void deletion_queue::flush()
{
while (false == m_entries.empty())
{
auto deleter{std::move(m_entries.front().second)};
m_entries.pop_front();
deleter();
}
}
// frame ring
bool frame_ring::create(allocator const &Allocator, VkDeviceSize SizePerFrame, std::uint32_t Frames, VkBufferUsageFlags Usage, std::string const &Name)
{
m_sizeperframe = SizePerFrame;
m_base = 0;
m_used = 0;
return m_buffer.create(Allocator, SizePerFrame * Frames, Usage, memory_kind::upload, Name) && (m_buffer.mapped() != nullptr);
}
void frame_ring::destroy()
{
m_buffer.destroy();
m_sizeperframe = 0;
m_used = 0;
}
void frame_ring::begin_frame(std::uint32_t Frame)
{
m_base = m_sizeperframe * Frame;
m_used = 0;
}
ring_slice frame_ring::allocate(VkDeviceSize Size, VkDeviceSize Alignment)
{
auto const alignment{std::max<VkDeviceSize>(Alignment, 4)};
auto const offset{(m_used + alignment - 1) / alignment * alignment};
if ((m_buffer.mapped() == nullptr) || (offset + Size > m_sizeperframe))
{
return {};
}
m_used = offset + Size;
ring_slice slice;
slice.buffer = m_buffer.handle();
slice.offset = m_base + offset;
slice.size = Size;
slice.data = m_buffer.mapped() + slice.offset;
return slice;
}
void frame_ring::flush() const
{
m_buffer.flush(m_base, m_used);
}
} // namespace vk14

View File

@@ -0,0 +1,241 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the memory of the vulkan renderer: the allocator (VMA), buffers and images which give their memory back when they go, the
// ring each frame in flight streams its data through, the uploads of data to the gpu through staging buffers, and the queue
// of objects deleted once the gpu is done with the frames which used them (by the value of the timeline semaphore)
#pragma once
#include "rendering/vulkan14/vulkan14common.h"
#include <deque>
#include <functional>
#include <vk_mem_alloc.h>
namespace vk14
{
class instance;
class device;
// the allocator of the device memory
class allocator
{
public:
allocator() = default;
allocator(allocator const &) = delete;
allocator &operator=(allocator const &) = delete;
~allocator();
bool create(instance const &Instance, device const &Device, std::string &Reason);
void destroy();
VmaAllocator handle() const
{
return m_allocator;
}
VkDevice device_handle() const
{
return m_device;
}
// device memory used and the budget of the heaps which are local to the device, bytes
void budget(VkDeviceSize &Usage, VkDeviceSize &Budget) const;
private:
VmaAllocator m_allocator{VK_NULL_HANDLE};
VkDevice m_device{VK_NULL_HANDLE};
std::uint32_t m_heapcount{0};
std::vector<bool> m_deviceheaps;
};
// where the memory of a buffer is
enum class memory_kind
{
device, // device local, filled by copies
upload, // seen by the cpu, written in order (staging, streamed data); device local where the device has such memory
readback // seen by the cpu, cached, for the data read back from the gpu
};
class buffer
{
public:
buffer() = default;
buffer(buffer const &) = delete;
buffer &operator=(buffer const &) = delete;
buffer(buffer &&Other) noexcept;
buffer &operator=(buffer &&Other) noexcept;
~buffer();
bool create(allocator const &Allocator, VkDeviceSize Size, VkBufferUsageFlags Usage, memory_kind Kind, std::string const &Name = {});
void destroy();
VkBuffer handle() const
{
return m_buffer;
}
VkDeviceSize size() const
{
return m_size;
}
// the memory as the cpu sees it, null for memory the cpu doesn't see. the upload and readback memory stays mapped
std::uint8_t *mapped() const
{
return m_mapped;
}
// makes the writes of the cpu seen by the gpu where the memory isn't coherent
void flush(VkDeviceSize Offset, VkDeviceSize Size) const;
explicit operator bool() const
{
return m_buffer != VK_NULL_HANDLE;
}
private:
VmaAllocator m_allocator{VK_NULL_HANDLE};
VkBuffer m_buffer{VK_NULL_HANDLE};
VmaAllocation m_allocation{VK_NULL_HANDLE};
VkDeviceSize m_size{0};
std::uint8_t *m_mapped{nullptr};
};
// the state of an image the barriers start from: its layout and the last stage and access which used it
struct image_state
{
VkImageLayout layout{VK_IMAGE_LAYOUT_UNDEFINED};
VkPipelineStageFlags2 stage{VK_PIPELINE_STAGE_2_NONE};
VkAccessFlags2 access{VK_ACCESS_2_NONE};
};
struct image_desc
{
VkImageType type{VK_IMAGE_TYPE_2D};
VkImageViewType view_type{VK_IMAGE_VIEW_TYPE_2D};
VkFormat format{VK_FORMAT_R8G8B8A8_UNORM};
VkExtent3D extent{1, 1, 1};
std::uint32_t mips{1};
std::uint32_t layers{1};
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
VkImageUsageFlags usage{VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT};
VkImageCreateFlags flags{0};
// the channels the default view reads, e.g. a single channel texture seen as rrr1
VkComponentMapping components{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY};
};
class image
{
public:
image() = default;
image(image const &) = delete;
image &operator=(image const &) = delete;
~image();
// a device local image with a view of all its levels and layers
bool create(allocator const &Allocator, image_desc const &Desc, std::string const &Name = {});
void destroy();
VkImage handle() const
{
return m_image;
}
VkImageView view() const
{
return m_view;
}
image_desc const &desc() const
{
return m_desc;
}
VkImageAspectFlags aspect() const;
VkDeviceSize bytes() const
{
return m_bytes;
}
// the state the next barrier starts from (the pass list and the uploads keep it)
image_state state{};
explicit operator bool() const
{
return m_image != VK_NULL_HANDLE;
}
private:
VmaAllocator m_allocator{VK_NULL_HANDLE};
VkDevice m_device{VK_NULL_HANDLE};
VkImage m_image{VK_NULL_HANDLE};
VkImageView m_view{VK_NULL_HANDLE};
VmaAllocation m_allocation{VK_NULL_HANDLE};
VkDeviceSize m_bytes{0};
image_desc m_desc;
};
// a barrier of an image from its state to the given one, the state of the image updated. nothing when the image already is
// in the layout and was only read by the same stage
void transition(VkCommandBuffer Commands, image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access);
// objects destroyed once the gpu is done with the frame which used them last
class deletion_queue
{
public:
// Value: the value of the timeline semaphore the gpu reaches when it's done with the object
void push(std::uint64_t Value, std::function<void()> &&Deleter);
// runs the deleters of the values the gpu reached
void collect(std::uint64_t Completed);
// runs all of them (the device is idle)
void flush();
std::size_t size() const
{
return m_entries.size();
}
private:
std::deque<std::pair<std::uint64_t, std::function<void()>>> m_entries;
};
// a part of the ring of a frame: where the data goes on the cpu, and where the gpu reads it
struct ring_slice
{
std::uint8_t *data{nullptr};
VkBuffer buffer{VK_NULL_HANDLE};
VkDeviceSize offset{0};
VkDeviceSize size{0};
explicit operator bool() const
{
return data != nullptr;
}
};
// the data streamed by the frames: one mapped buffer split into a part for each frame in flight, the part of a frame taken
// from the start again once the gpu is done with the frame which used it before (the counterpart of gl::frame_stream)
class frame_ring
{
public:
bool create(allocator const &Allocator, VkDeviceSize SizePerFrame, std::uint32_t Frames, VkBufferUsageFlags Usage, std::string const &Name);
void destroy();
// the part of the frame Frame is used from its start again
void begin_frame(std::uint32_t Frame);
// Size bytes aligned to Alignment; empty when the part of the frame is full
ring_slice allocate(VkDeviceSize Size, VkDeviceSize Alignment);
// makes the writes of the frame seen by the gpu, where the memory isn't coherent
void flush() const;
VkDeviceSize used() const
{
return m_used;
}
VkDeviceSize capacity() const
{
return m_sizeperframe;
}
private:
buffer m_buffer;
VkDeviceSize m_sizeperframe{0};
VkDeviceSize m_base{0};
VkDeviceSize m_used{0};
};
} // namespace vk14

View File

@@ -0,0 +1,237 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14passes.h"
#include "rendering/vulkan14/vulkan14device.h"
namespace vk14
{
image_use use_of(image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access)
{
return image_use{Image.handle(), Image.aspect(), &Image.state, Layout, Stage, Access};
}
void barriers(VkCommandBuffer Commands, std::vector<image_use> const &Uses)
{
auto const writes{VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT |
VK_ACCESS_2_HOST_WRITE_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT};
std::vector<VkImageMemoryBarrier2> list;
for (auto const &use : Uses)
{
if ((use.image == VK_NULL_HANDLE) || (use.state == nullptr))
{
continue;
}
auto &state{*use.state};
// reads after reads in the same layout need nothing
if ((state.layout == use.layout) && ((state.access & writes) == 0) && ((use.access & writes) == 0))
{
state.stage |= use.stage;
state.access |= use.access;
continue;
}
VkImageMemoryBarrier2 barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
barrier.srcStageMask = (state.stage != VK_PIPELINE_STAGE_2_NONE ? state.stage : VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT);
barrier.srcAccessMask = (state.access & writes);
barrier.dstStageMask = use.stage;
barrier.dstAccessMask = use.access;
barrier.oldLayout = state.layout;
barrier.newLayout = use.layout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = use.image;
barrier.subresourceRange = {use.aspect, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS};
list.push_back(barrier);
state = image_state{use.layout, use.stage, use.access};
}
if (list.empty())
{
return;
}
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.imageMemoryBarrierCount = static_cast<std::uint32_t>(list.size());
dependency.pImageMemoryBarriers = list.data();
vkCmdPipelineBarrier2(Commands, &dependency);
}
pass_list::~pass_list()
{
destroy();
}
bool pass_list::create(device const &Device, std::uint32_t Frames, std::string &Reason)
{
m_device = Device.handle();
m_period = Device.caps().timestamp_period;
// the bits of the timestamps of the queue the frames go to
std::uint32_t count{0};
vkGetPhysicalDeviceQueueFamilyProperties(Device.physical(), &count, nullptr);
std::vector<VkQueueFamilyProperties> families(count);
vkGetPhysicalDeviceQueueFamilyProperties(Device.physical(), &count, families.data());
auto const bits{(Device.families().graphics < count) ? families[Device.families().graphics].timestampValidBits : 64u};
m_mask = (bits >= 64 ? ~0ull : ((1ull << bits) - 1));
m_frames.resize(Frames);
for (std::uint32_t i = 0; i < Frames; ++i)
{
VkQueryPoolCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
info.queryType = VK_QUERY_TYPE_TIMESTAMP;
info.queryCount = max_passes * 2;
if (!VK14_VERIFY(vkCreateQueryPool(m_device, &info, nullptr, &m_frames[i].pool)))
{
m_frames[i].pool = VK_NULL_HANDLE;
Reason = "no query pool for the times of the passes";
return false;
}
// the queries are reset from the host before their first use (hostQueryReset)
vkResetQueryPool(m_device, m_frames[i].pool, 0, info.queryCount);
name_object(m_device, VK_OBJECT_TYPE_QUERY_POOL, handle_value(m_frames[i].pool), "pass times " + std::to_string(i));
}
return true;
}
void pass_list::destroy()
{
for (auto &frame : m_frames)
{
if (frame.pool != VK_NULL_HANDLE)
{
vkDestroyQueryPool(m_device, frame.pool, nullptr);
}
}
m_frames.clear();
m_device = VK_NULL_HANDLE;
}
void pass_list::begin_frame(VkCommandBuffer Commands, std::uint32_t Frame, std::uint64_t Number)
{
m_commands = Commands;
m_frame = Frame;
m_open = -1;
auto &frame{m_frames[Frame]};
frame.names.clear();
frame.used = 0;
frame.number = Number;
frame.recorded = true;
vkCmdResetQueryPool(Commands, frame.pool, 0, max_passes * 2);
}
void pass_list::begin_pass(char const *Name, std::vector<image_use> const &Uses)
{
if (m_open >= 0)
{
end_pass();
}
barriers(m_commands, Uses);
begin_label(m_commands, Name);
auto &frame{m_frames[m_frame]};
// (no timestamps on a queue without valid bits; the device is chosen with them, this is for safety)
if ((frame.used < max_passes) && (m_mask != 0))
{
vkCmdWriteTimestamp2(m_commands, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, frame.pool, frame.used * 2);
frame.names.emplace_back(Name);
m_open = static_cast<int>(frame.used);
++frame.used;
}
else
{
m_open = static_cast<int>(max_passes);
}
}
void pass_list::end_pass()
{
if (m_open < 0)
{
return;
}
if (m_open < static_cast<int>(max_passes))
{
vkCmdWriteTimestamp2(m_commands, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, m_frames[m_frame].pool, static_cast<std::uint32_t>(m_open) * 2 + 1);
}
end_label(m_commands);
m_open = -1;
}
void pass_list::end_frame()
{
end_pass();
m_commands = VK_NULL_HANDLE;
}
void pass_list::collect(std::uint32_t Frame)
{
auto &frame{m_frames[Frame]};
if ((false == frame.recorded) || (frame.used == 0))
{
return;
}
frame.recorded = false;
std::vector<std::uint64_t> stamps(frame.used * 2);
auto const result{vkGetQueryPoolResults(m_device, frame.pool, 0, frame.used * 2, stamps.size() * sizeof(std::uint64_t), stamps.data(), sizeof(std::uint64_t), VK_QUERY_RESULT_64_BIT)};
if (result != VK_SUCCESS)
{
return;
}
// a running average, so the numbers can be read
auto const blend = [](double &Average, double const Value) { Average = (Average <= 0.0 ? Value : Average * 0.9 + Value * 0.1); };
double total{0.0};
for (std::uint32_t i = 0; i < frame.used; ++i)
{
auto const ticks{((stamps[i * 2 + 1] & m_mask) - (stamps[i * 2] & m_mask)) & m_mask};
auto const ms{static_cast<double>(ticks) * m_period / 1000000.0};
total += ms;
auto lookup{std::find_if(m_times.begin(), m_times.end(), [&](auto const &Entry) { return Entry.first == frame.names[i]; })};
if (lookup == m_times.end())
{
m_times.emplace_back(frame.names[i], 0.0);
lookup = std::prev(m_times.end());
}
blend(lookup->second, ms);
}
blend(m_framems, total);
}
std::string pass_list::passes_of(std::uint64_t Number) const
{
std::string result;
for (auto const &frame : m_frames)
{
if (frame.number != Number)
{
continue;
}
for (auto const &name : frame.names)
{
result += (result.empty() ? "" : ", ") + name;
}
}
return (result.empty() ? std::string("(not known)") : result);
}
std::string pass_list::info() const
{
std::string text;
char buffer[64] = {};
for (auto const &[name, ms] : m_times)
{
std::snprintf(buffer, sizeof(buffer), " %-14s %7.3f ms\n", name.c_str(), ms);
text += buffer;
}
std::snprintf(buffer, sizeof(buffer), " %-14s %7.3f ms\n", "total", m_framems);
text += buffer;
return text;
}
} // namespace vk14

View File

@@ -0,0 +1,96 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the passes of a frame (decision K12 of the plan): a list in the fixed order the frame makes them, each pass saying which images
// it uses and how. the barriers between the passes (sync2) are worked out from the state each image was left in, there's no
// graph. each pass is a label for the captures and the validation layer (debug utils), and is timed on the gpu: the times
// of the passes of the frames done go to the debug panel under the same names as the parts of the gpu frame of the opengl
// renderer. when the device is lost, the log tells which passes of which frame were on the gpu
#pragma once
#include "rendering/vulkan14/vulkan14common.h"
#include "rendering/vulkan14/vulkan14memory.h"
namespace vk14
{
class device;
// an image a pass uses, and how
struct image_use
{
VkImage image{VK_NULL_HANDLE};
VkImageAspectFlags aspect{VK_IMAGE_ASPECT_COLOR_BIT};
// where the state of the image is kept: by the image itself, or by the swapchain for its images
image_state *state{nullptr};
VkImageLayout layout{VK_IMAGE_LAYOUT_UNDEFINED};
VkPipelineStageFlags2 stage{VK_PIPELINE_STAGE_2_NONE};
VkAccessFlags2 access{VK_ACCESS_2_NONE};
};
// the use of a vk14::image
image_use use_of(image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access);
// the barriers which bring the images from their states to the uses, the states updated
void barriers(VkCommandBuffer Commands, std::vector<image_use> const &Uses);
class pass_list
{
public:
pass_list() = default;
pass_list(pass_list const &) = delete;
pass_list &operator=(pass_list const &) = delete;
~pass_list();
bool create(device const &Device, std::uint32_t Frames, std::string &Reason);
void destroy();
// the frame Frame (index of the frame in flight) is recorded to Commands from now on; Number counts the frames
void begin_frame(VkCommandBuffer Commands, std::uint32_t Frame, std::uint64_t Number);
// a pass: the barriers of its images, its label and the timestamp of its start
void begin_pass(char const *Name, std::vector<image_use> const &Uses = {});
void end_pass();
void end_frame();
// the gpu times of the frame Frame, once the gpu is done with it (after the wait for its value of the timeline)
void collect(std::uint32_t Frame);
// the passes recorded in the frame done last, and the ones of the frame Number, for the report of a lost device
std::string passes_of(std::uint64_t Number) const;
// the gpu times (ms) of the parts of the frame, averaged, as the opengl renderer gives them: "name: x.xx ms" lines
std::string info() const;
double frame_ms() const
{
return m_framems;
}
private:
struct frame_queries
{
VkQueryPool pool{VK_NULL_HANDLE};
std::vector<std::string> names;
std::uint32_t used{0};
std::uint64_t number{0};
bool recorded{false};
};
static constexpr std::uint32_t max_passes{32};
VkDevice m_device{VK_NULL_HANDLE};
float m_period{1.f}; // ns per tick
std::uint64_t m_mask{~0ull}; // the valid bits of the timestamps
std::vector<frame_queries> m_frames;
VkCommandBuffer m_commands{VK_NULL_HANDLE};
std::uint32_t m_frame{0};
int m_open{-1};
// averaged times of the parts, ms
std::vector<std::pair<std::string, double>> m_times;
double m_framems{0.0};
};
} // namespace vk14

View File

@@ -13,20 +13,33 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/vulkan14/vulkan14instance.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "rendering/vulkan14/vulkan14swapchain.h"
#include "rendering/vulkan14/vulkan14memory.h"
#include "rendering/vulkan14/vulkan14shaders.h"
#include "rendering/vulkan14/vulkan14descriptors.h"
#include "rendering/vulkan14/vulkan14passes.h"
#include "rendering/vulkan14/vulkan14texture.h"
#include "rendering/vulkan14/vulkan14imgui.h"
#include "rendering/vulkan14/vulkan14bindings.h"
#include "rendering/nullrenderer.h"
#include "application/application.h"
#include "simulation/simulation.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/Timer.h"
#include "utilities/crashreporter.h"
#include "utilities/utilities.h"
#include "imgui/imgui.h"
namespace
{
// the colour the windows are cleared to, the initial background of the opengl renderer (srgb)
// the colour the windows are cleared to once a scenery is loaded, until the scenery is drawn (stage D3 of the plan): the initial
// background of the opengl renderer (srgb). before that the windows are black, as with the opengl renderer
glm::vec3 const clear_colour{51.f / 255.f, 102.f / 255.f, 85.f / 255.f};
// the ring each frame in flight streams its data through (the user interface for now)
VkDeviceSize const ring_size{8 * 1024 * 1024};
float srgb_to_linear(float const Value)
{
return (Value <= 0.04045f ? Value / 12.92f : std::pow((Value + 0.055f) / 1.055f, 2.4f));
@@ -40,13 +53,31 @@ struct vulkan14_renderer::context
struct frame
{
VkCommandPool pool{VK_NULL_HANDLE};
// the uploads of the frame go to the queue first, then the commands of the frame
VkCommandBuffer uploads{VK_NULL_HANDLE};
VkCommandBuffer commands{VK_NULL_HANDLE};
// the value of the timeline semaphore the gpu reaches when it's done with the frame; 0: never submitted
std::uint64_t done{0};
// the number of the frame, for the report of a lost device
std::uint64_t number{0};
};
vk14::instance instance;
vk14::device device;
vk14::allocator memory;
vk14::deletion_queue deletions;
vk14::uploader uploads;
vk14::image_table images;
vk14::samplers sampling;
vk14::frame_ring ring;
vk14::shader_library shaders;
vk14::pipeline_library pipelines;
vk14::pass_list passes;
vk14::pending_textures pending;
vk14::resources resources;
vk14::imgui_backend imgui;
// the textures of the files; made after the objects above and gone before them
std::unique_ptr<texture_bank> textures;
// [0]: the main window, then the windows of the extra viewports
std::vector<std::unique_ptr<vk14::swapchain>> windows;
std::vector<frame> frames;
@@ -94,21 +125,71 @@ struct vulkan14_renderer::context
Reason = "no command pool";
return false;
}
VkCommandBuffer buffers[2] = {VK_NULL_HANDLE, VK_NULL_HANDLE};
VkCommandBufferAllocateInfo bufferinfo{};
bufferinfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
bufferinfo.commandPool = frames[i].pool;
bufferinfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
bufferinfo.commandBufferCount = 1;
if (!VK14_VERIFY(vkAllocateCommandBuffers(handle, &bufferinfo, &frames[i].commands)))
bufferinfo.commandBufferCount = 2;
if (!VK14_VERIFY(vkAllocateCommandBuffers(handle, &bufferinfo, buffers)))
{
Reason = "no command buffer";
return false;
}
frames[i].uploads = buffers[0];
frames[i].commands = buffers[1];
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].uploads), "uploads " + std::to_string(i));
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].commands), "frame " + std::to_string(i));
}
return true;
}
// the parts of the renderer made once the device is there
bool create_core(std::uint32_t const Frames, std::string &Reason)
{
if (false == memory.create(instance, device, Reason))
{
return false;
}
uploads.init(memory, deletions, device.physical());
if (false == sampling.create(device, static_cast<float>(Global.AnisotropicFiltering), Reason))
{
return false;
}
auto const &limits{device.caps().limits};
(void)limits;
if (false == ring.create(memory, ring_size, Frames, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, "frame ring"))
{
Reason = "no ring for the data of the frames";
return false;
}
if ((false == shaders.init(vk14::binding_preamble(), Reason)) || (false == pipelines.init(device, Reason)) || (false == passes.create(device, Frames, Reason)))
{
return false;
}
resources.gpu = &device;
resources.memory = &memory;
resources.uploads = &uploads;
resources.deletions = &deletions;
resources.sampling = &sampling;
resources.images = &images;
resources.thread = std::this_thread::get_id();
resources.pending = &pending;
textures = std::make_unique<texture_bank>([this]() -> texture_source * { return new vk14::vulkan14_texture(resources); });
return imgui.create(resources, shaders, pipelines, ring, Reason);
}
// the value of the timeline the gpu reached, 0 when it can't be read
std::uint64_t completed() const
{
std::uint64_t value{0};
if ((timeline == VK_NULL_HANDLE) || (vkGetSemaphoreCounterValue(device.handle(), timeline, &value) != VK_SUCCESS))
{
return 0;
}
return value;
}
void destroy()
{
auto const handle{device.handle()};
@@ -119,8 +200,20 @@ struct vulkan14_renderer::context
}
acquired.clear();
windows.clear();
imgui.destroy();
// the textures give their images to the deletion queue, which goes now that the gpu is idle
textures.reset();
uploads.end_frame();
uploads = vk14::uploader{};
images.clear();
deletions.flush();
pipelines.shutdown();
shaders.shutdown();
passes.destroy();
ring.destroy();
if (handle != VK_NULL_HANDLE)
{
sampling.destroy(handle);
for (auto &frame : frames)
{
if (frame.pool != VK_NULL_HANDLE)
@@ -133,6 +226,7 @@ struct vulkan14_renderer::context
vkDestroySemaphore(handle, timeline, nullptr);
}
}
memory.destroy();
frames.clear();
timeline = VK_NULL_HANDLE;
device.destroy();
@@ -211,7 +305,8 @@ bool vulkan14_renderer::Init(GLFWwindow *Window)
config.debug_utils = (Global.gfx_vulkan_validation > 0);
auto const framesinflight{static_cast<std::uint32_t>(std::clamp(Global.gfx_vulkan_framesinflight, 2, 3))};
auto ok{ctx.instance.create(config, reason) && ctx.device.select(ctx.instance.handle(), VK_NULL_HANDLE, Global.gfx_vulkan_device, false, reason) && ctx.device.create(ctx.instance, reason) && ctx.create_frames(framesinflight, reason)};
auto ok{ctx.instance.create(config, reason) && ctx.device.select(ctx.instance.handle(), VK_NULL_HANDLE, Global.gfx_vulkan_device, false, reason) && ctx.device.create(ctx.instance, reason) && ctx.create_frames(framesinflight, reason) &&
ctx.create_core(framesinflight, reason)};
if (ok)
{
auto mainwindow{std::make_unique<vk14::swapchain>()};
@@ -227,7 +322,7 @@ bool vulkan14_renderer::Init(GLFWwindow *Window)
vk14::log_info(std::to_string(framesinflight) + " frames in flight");
// until the stages of the plan bring them, these parts of the simulator aren't there with vulkan
vk14::log_info("not available with vulkan yet: the scenery and the vehicles (D1-D7), the user interface (R5), picking, mouse position in the scene and screenshots (D6), "
vk14::log_info("not available with vulkan yet: the scenery and the vehicles (D1-D7), picking, mouse position in the scene and screenshots (D6), the map panel, "
"python screens in their own windows (N5), panels in their own windows (R5b), VR (X4)");
WriteLog("Gfx Renderer: setup complete");
update_info();
@@ -271,6 +366,27 @@ void vulkan14_renderer::Shutdown()
m_context.reset();
}
void vulkan14_renderer::report_device_lost(char const *Where)
{
auto &ctx{*m_context};
if (ctx.devicelost)
{
return;
}
ctx.devicelost = true;
auto const done{ctx.completed()};
std::string report{std::string("the device was lost (") + Where + "); the gpu was done with the timeline value " + std::to_string(done) + " of " + std::to_string(ctx.timelinevalue)};
for (auto const &frame : ctx.frames)
{
if ((frame.done > done) && (frame.done <= ctx.timelinevalue))
{
report += "; frame " + std::to_string(frame.number) + " (value " + std::to_string(frame.done) + ") wasn't done, its passes: " + ctx.passes.passes_of(frame.number);
}
}
vk14::log_error(report);
crashreport_add_info("vulkan_device_lost", report);
}
bool vulkan14_renderer::Render()
{
if (!m_context || m_context->devicelost)
@@ -294,11 +410,35 @@ bool vulkan14_renderer::Render()
wait.pValues = &frame.done;
if (!VK14_VERIFY(vkWaitSemaphores(device, &wait, UINT64_MAX)))
{
ctx.devicelost = true;
report_device_lost("waiting for a frame");
return false;
}
ctx.passes.collect(slot);
}
// what the frames done used last goes
ctx.deletions.collect(std::max(ctx.completed(), frame.done));
VK14_VERIFY(vkResetCommandPool(device, frame.pool, 0));
ctx.ring.begin_frame(slot);
VkCommandBufferBeginInfo begin{};
begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
VK14_VERIFY(vkBeginCommandBuffer(frame.uploads, &begin));
VK14_VERIFY(vkBeginCommandBuffer(frame.commands, &begin));
auto const value{ctx.timelinevalue + 1};
ctx.uploads.begin_frame(frame.uploads, value);
// the content other threads gave the textures (python)
{
std::vector<vk14::vulkan14_texture *> pending;
{
std::lock_guard<std::mutex> lock(ctx.pending.mutex);
pending.swap(ctx.pending.textures);
}
for (auto *texture : pending)
{
texture->apply_pending();
}
}
ctx.acquired.clear();
for (auto &window : ctx.windows)
@@ -311,26 +451,61 @@ bool vulkan14_renderer::Render()
case vk14::swapchain::acquire_result::skipped:
break;
default:
ctx.devicelost = true;
report_device_lost("acquiring an image");
return false;
}
}
// the frames of the user interface go on (its input works too), though nothing of it is drawn yet
// the user interface: imgui makes its frame, the images it asks for are uploaded in this frame
Timer::subsystem.gfx_gui.start();
Application.render_ui();
Timer::subsystem.gfx_gui.stop();
VkCommandBufferBeginInfo begin{};
begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
VK14_VERIFY(vkBeginCommandBuffer(frame.commands, &begin));
frame.number = ctx.framecount;
ctx.passes.begin_frame(frame.commands, slot, ctx.framecount);
auto const background{simulation::is_ready ? clear_colour : glm::vec3{0.f}};
bool uidrawn{false};
for (auto *window : ctx.acquired)
{
auto const colour{window->srgb() ? glm::vec3{srgb_to_linear(clear_colour.r), srgb_to_linear(clear_colour.g), srgb_to_linear(clear_colour.b)} : clear_colour};
window->record_clear(frame.commands, glm::vec4{colour, 1.f});
auto const colour{window->srgb() ? glm::vec3{srgb_to_linear(background.r), srgb_to_linear(background.g), srgb_to_linear(background.b)} : background};
if (window != ctx.windows.front().get())
{
// the extra viewports: their colour only, until the scenery is drawn into them (D6)
window->record_clear(frame.commands, glm::vec4{colour, 1.f});
continue;
}
// the main window: cleared, the user interface over it, shown
ctx.passes.begin_pass("ui", {window->use(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT)});
VkRenderingAttachmentInfo attachment{};
attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
attachment.imageView = window->view();
attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment.clearValue.color = {{colour.r, colour.g, colour.b, 1.f}};
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, window->extent()};
rendering.layerCount = 1;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &attachment;
vkCmdBeginRendering(frame.commands, &rendering);
ctx.imgui.record(frame.commands, window->extent(), window->format(), window->srgb());
vkCmdEndRendering(frame.commands);
ctx.passes.end_pass();
vk14::barriers(frame.commands, {window->use(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_PIPELINE_STAGE_2_NONE, VK_ACCESS_2_NONE)});
uidrawn = true;
}
if (false == uidrawn)
{
// the main window is minimized: the frame of imgui has nowhere to go
ctx.imgui.skip();
}
ctx.passes.end_frame();
auto const uploaded{ctx.uploads.end_frame()};
VK14_VERIFY(vkEndCommandBuffer(frame.uploads));
VK14_VERIFY(vkEndCommandBuffer(frame.commands));
ctx.ring.flush();
std::vector<VkSemaphoreSubmitInfo> waits;
std::vector<VkSemaphoreSubmitInfo> signals;
@@ -351,28 +526,40 @@ bool vulkan14_renderer::Render()
VkSemaphoreSubmitInfo timelinesignal{};
timelinesignal.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
timelinesignal.semaphore = ctx.timeline;
timelinesignal.value = ctx.timelinevalue + 1;
timelinesignal.value = value;
timelinesignal.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
signals.push_back(timelinesignal);
VkCommandBufferSubmitInfo commands{};
commands.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
commands.commandBuffer = frame.commands;
// the uploads first, in the same submit: their barriers make them done before the frame samples the images
std::vector<VkCommandBufferSubmitInfo> commands;
if (uploaded)
{
VkCommandBufferSubmitInfo info{};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
info.commandBuffer = frame.uploads;
commands.push_back(info);
}
{
VkCommandBufferSubmitInfo info{};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
info.commandBuffer = frame.commands;
commands.push_back(info);
}
VkSubmitInfo2 submit{};
submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2;
submit.waitSemaphoreInfoCount = static_cast<std::uint32_t>(waits.size());
submit.pWaitSemaphoreInfos = waits.data();
submit.commandBufferInfoCount = 1;
submit.pCommandBufferInfos = &commands;
submit.commandBufferInfoCount = static_cast<std::uint32_t>(commands.size());
submit.pCommandBufferInfos = commands.data();
submit.signalSemaphoreInfoCount = static_cast<std::uint32_t>(signals.size());
submit.pSignalSemaphoreInfos = signals.data();
if (!VK14_VERIFY(vkQueueSubmit2(ctx.device.graphics_queue(), 1, &submit, VK_NULL_HANDLE)))
{
ctx.devicelost = true;
report_device_lost("submitting a frame");
return false;
}
frame.done = ++ctx.timelinevalue;
frame.done = ctx.timelinevalue = value;
Timer::subsystem.gfx_total.stop();
return true;
@@ -415,7 +602,7 @@ void vulkan14_renderer::SwapBuffers()
if ((result == VK_ERROR_DEVICE_LOST) || (result == VK_ERROR_SURFACE_LOST_KHR))
{
vk14::log_error(std::string("vkQueuePresentKHR failed with ") + vk14::result_string(result));
ctx.devicelost = true;
report_device_lost("presenting");
}
ctx.acquired.clear();
}
@@ -475,7 +662,7 @@ gfx::userdata_array const &vulkan14_renderer::UserData(gfx::geometry_handle cons
return m_geometry.userdata(Geometry);
}
// materials, shaders, textures: none until D1 and D2
// materials and their shaders: none until D1
material_handle vulkan14_renderer::Fetch_Material(std::string const &Filename, bool const Loadnow)
{
@@ -489,28 +676,44 @@ IMaterial const *vulkan14_renderer::Material(material_handle const Material) con
return IMaterial::null_material();
}
std::shared_ptr<gl::program> vulkan14_renderer::Fetch_Shader(std::string const &name)
std::shared_ptr<gfx::shader_program> vulkan14_renderer::Fetch_Shader(std::string const &name)
{
return nullptr;
}
// textures: the files read and decoded as for the opengl renderer, made into images when they are first used (create(), e.g. by the
// launcher); the materials of the scene take them from D1
texture_handle vulkan14_renderer::Fetch_Texture(std::string const &Filename, bool const Loadnow, GLint format_hint)
{
return null_handle;
if (!m_context || !m_context->textures)
{
return null_handle;
}
return m_context->textures->create(Filename, Loadnow, format_hint);
}
// the shaders take the textures by the pushed descriptors (D1), there's nothing to bind
void vulkan14_renderer::Bind_Texture(texture_handle const Texture) {}
void vulkan14_renderer::Bind_Texture(std::size_t const Unit, texture_handle const Texture) {}
ITexture &vulkan14_renderer::Texture(texture_handle const Texture)
{
return *ITexture::null_texture();
if ((Texture == null_handle) || !m_context || !m_context->textures)
{
return *ITexture::null_texture();
}
return m_context->textures->mark_as_used(Texture);
}
ITexture const &vulkan14_renderer::Texture(texture_handle const Texture) const
{
return *ITexture::null_texture();
if ((Texture == null_handle) || !m_context || !m_context->textures)
{
return *ITexture::null_texture();
}
return m_context->textures->texture(Texture);
}
// picking: D6
@@ -538,6 +741,11 @@ glm::dvec3 vulkan14_renderer::Mouse_Position() const
void vulkan14_renderer::Update(double const Deltatime)
{
if (m_context && m_context->textures && (true == Global.ResourceSweep) && (true == simulation::is_ready))
{
// textures nothing used for a while give their images back
m_context->textures->update();
}
m_updateaccumulator += Deltatime;
if (m_updateaccumulator < 1.0)
{
@@ -559,7 +767,13 @@ void vulkan14_renderer::update_info()
auto const &ctx{*m_context};
auto const &caps{ctx.device.caps()};
m_infotimes = "vulkan " + vk14::version_string(caps.api_version) + ": " + caps.device_name + " (" + caps.driver_name + ")\n" + "frames: " + std::to_string(ctx.framecount) + ", in flight " + std::to_string(ctx.frames.size()) + "\n" +
"mainloop total: " + to_string(Timer::subsystem.mainloop_total.average(), 2) + " ms, frame cpu: " + to_string(Timer::subsystem.gfx_total.average(), 2) + " ms, present: " + to_string(Timer::subsystem.gfx_swap.average(), 2) + " ms\n";
"mainloop total: " + to_string(Timer::subsystem.mainloop_total.average(), 2) + " ms, frame cpu: " + to_string(Timer::subsystem.gfx_total.average(), 2) + " ms, ui: " + to_string(Timer::subsystem.gfx_gui.average(), 2) +
" ms, present: " + to_string(Timer::subsystem.gfx_swap.average(), 2) + " ms\n" + "gpu frame parts:\n" + ctx.passes.info();
VkDeviceSize usage{0}, budget{0};
ctx.memory.budget(usage, budget);
m_infostats = "device memory: " + vk14::memory_size_string(usage) + " of " + vk14::memory_size_string(budget) + (ctx.device.memory_budget_enabled() ? "" : " (no budget from the driver)") + "\n" + "images: " + std::to_string(ctx.images.size()) +
", uploaded " + vk14::memory_size_string(ctx.uploads.uploaded()) + ", waiting for the gpu to be deleted: " + std::to_string(ctx.deletions.size()) + "\n" + (ctx.textures ? ctx.textures->info() + "\n" : std::string{}) + ctx.shaders.info() +
"\n" + ctx.pipelines.info() + "\n" + "frame ring: " + vk14::memory_size_string(ctx.ring.used()) + " of " + vk14::memory_size_string(ctx.ring.capacity()) + "\n";
for (auto const &window : ctx.windows)
{
auto const extent{window->extent()};
@@ -597,32 +811,10 @@ std::string const &vulkan14_renderer::info_stats() const
imgui_renderer *vulkan14_renderer::GetImguiRenderer()
{
return &m_imgui;
return (m_context ? &m_context->imgui : nullptr);
}
void vulkan14_renderer::MakeScreenshot()
{
vk14::log_warning("screenshots aren't made by the vulkan renderer yet (D6)");
}
// imgui: the font atlas is built so its frames go on; nothing is drawn until R5
bool vulkan14_renderer::imgui_stub::Init()
{
auto &io{ImGui::GetIO()};
io.BackendRendererName = "vulkan14 (no drawing yet)";
unsigned char *pixels{nullptr};
int width{0}, height{0};
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
io.Fonts->SetTexID(static_cast<ImTextureID>(0));
return true;
}
void vulkan14_renderer::imgui_stub::Shutdown()
{
ImGui::GetIO().BackendRendererName = nullptr;
}
void vulkan14_renderer::imgui_stub::BeginFrame() {}
void vulkan14_renderer::imgui_stub::Render() {}

View File

@@ -7,10 +7,11 @@ obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the vulkan 1.4 renderer, picked with gfxrenderer vulkan. so far the frame of the renderer only: the gpu, the windows with
// their swapchains, the frames in flight; each window is cleared to a colour. the scenery, the materials and the textures
// come in the later stages of the plan (claude/vulkan14-renderer-plan.md); until then those methods keep the data the
// simulation reads back and draw nothing.
// the vulkan 1.4 renderer, picked with gfxrenderer vulkan. so far the core of the renderer (the track R of the plan,
// claude/vulkan14-renderer-plan.md): the gpu, the windows with their swapchains, the frames in flight, the memory and the
// uploads, the shaders and the pipelines, the pushed descriptors, the passes with their barriers and gpu times, the textures
// and the user interface (imgui) with the launcher. the scenery and the materials come in the later stages of the plan; until
// then those methods keep the data the simulation reads back and draw nothing.
//
// the vulkan headers stay in the files of rendering/vulkan14: the objects of the api live in vulkan14_renderer::context
@@ -46,7 +47,7 @@ class vulkan14_renderer : public gfx_renderer
void Bind_Material(material_handle const Material, TSubModel const *sm = nullptr, lighting_data const *lighting = nullptr) override;
IMaterial const *Material(material_handle const Material) const override;
// shaders
std::shared_ptr<gl::program> Fetch_Shader(std::string const &name) override;
std::shared_ptr<gfx::shader_program> Fetch_Shader(std::string const &name) override;
// textures
texture_handle Fetch_Texture(std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA) override;
void Bind_Texture(texture_handle const Texture) override;
@@ -80,22 +81,13 @@ class vulkan14_renderer : public gfx_renderer
// the objects of the vulkan api (vulkan14renderer.cpp)
struct context;
// imgui without drawing: the frames of the ui go on (its input works), nothing is drawn until the stage R5 of the plan
class imgui_stub : public imgui_renderer
{
public:
bool Init() override;
void Shutdown() override;
void BeginFrame() override;
void Render() override;
};
void update_info();
// the device was lost: what was on the gpu goes to the log
void report_device_lost(char const *Where);
std::unique_ptr<context> m_context;
// the data of the geometry, which the simulation reads back; nothing on the gpu yet
gfx::geometrybank_manager m_geometry;
imgui_stub m_imgui;
float m_framerate{0.f};
double m_updateaccumulator{0.0};
bool m_debug_ui_active{false};

View File

@@ -0,0 +1,705 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14shaders.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/utilities.h"
#include <glslang/Public/ResourceLimits.h>
#include <glslang/Public/ShaderLang.h>
#include <SPIRV/GlslangToSpv.h>
#include <chrono>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <sstream>
namespace vk14
{
namespace
{
EShLanguage language_of(gfx::shader_stage const Stage)
{
switch (Stage)
{
case gfx::shader_stage::vertex:
return EShLangVertex;
case gfx::shader_stage::fragment:
return EShLangFragment;
case gfx::shader_stage::geometry:
return EShLangGeometry;
case gfx::shader_stage::compute:
return EShLangCompute;
}
return EShLangVertex;
}
VkShaderStageFlagBits stage_bit(gfx::shader_stage const Stage)
{
switch (Stage)
{
case gfx::shader_stage::vertex:
return VK_SHADER_STAGE_VERTEX_BIT;
case gfx::shader_stage::fragment:
return VK_SHADER_STAGE_FRAGMENT_BIT;
case gfx::shader_stage::geometry:
return VK_SHADER_STAGE_GEOMETRY_BIT;
case gfx::shader_stage::compute:
return VK_SHADER_STAGE_COMPUTE_BIT;
}
return VK_SHADER_STAGE_VERTEX_BIT;
}
std::string files_of(gfx::shader_source const &Source)
{
std::string result;
for (std::size_t i = 0; i < Source.files.size(); ++i)
{
result += (i > 0 ? ", " : "") + std::to_string(i) + ": " + Source.files[i];
}
return result;
}
std::vector<std::uint32_t> read_spirv(std::string const &Path)
{
std::ifstream file(Path, std::ios::binary | std::ios::ate);
if (false == file.is_open())
{
return {};
}
auto const size{static_cast<std::size_t>(file.tellg())};
if ((size < 20) || (size % 4 != 0))
{
return {};
}
std::vector<std::uint32_t> words(size / 4);
file.seekg(0);
file.read(reinterpret_cast<char *>(words.data()), static_cast<std::streamsize>(size));
if ((false == static_cast<bool>(file)) || (words[0] != 0x07230203u))
{
return {};
}
return words;
}
// written beside and moved into place, so a program reading the cache at the same time doesn't get half of the file
void write_file(std::string const &Path, void const *Data, std::size_t const Size)
{
std::error_code error;
std::filesystem::create_directories(std::filesystem::path(Path).parent_path(), error);
auto const temporary{Path + ".tmp"};
{
std::ofstream file(temporary, std::ios::binary | std::ios::trunc);
if (false == file.is_open())
{
return;
}
file.write(static_cast<char const *>(Data), static_cast<std::streamsize>(Size));
if (false == static_cast<bool>(file))
{
return;
}
}
std::filesystem::rename(temporary, Path, error);
if (error)
{
std::filesystem::remove(temporary, error);
}
}
std::string hex(std::uint64_t const Value)
{
char buffer[17] = {};
std::snprintf(buffer, sizeof(buffer), "%016llx", static_cast<unsigned long long>(Value));
return buffer;
}
// fnv-1a 64 over more data
void hash_bytes(std::uint64_t &Hash, void const *Data, std::size_t const Size)
{
auto const *bytes{static_cast<std::uint8_t const *>(Data)};
for (std::size_t i = 0; i < Size; ++i)
{
Hash ^= bytes[i];
Hash *= 1099511628211ull;
}
}
template <typename Type_> void hash_value(std::uint64_t &Hash, Type_ const &Value)
{
hash_bytes(Hash, &Value, sizeof(Value));
}
std::string user_cache_dir(std::string const &Name)
{
// with a separator at the end
auto const path{user_config_path("cache")};
auto const name{Name.empty() ? std::string{} : Name + "/"};
if (path.empty())
{
return "cache/vulkan14/" + name;
}
return (path / "vulkan14").string() + "/" + name;
}
} // namespace
// shader library
shader_library::~shader_library()
{
shutdown();
}
bool shader_library::init(std::string const &Preamble, std::string &Reason)
{
m_preamble = Preamble;
if (false == glslang::InitializeProcess())
{
Reason = "the shader compiler (glslang) can't start";
return false;
}
m_glslang = true;
auto const version{glslang::GetVersion()};
m_compilerversion = "glslang " + std::to_string(version.major) + "." + std::to_string(version.minor) + "." + std::to_string(version.patch) + " vulkan1.4 spirv1.6";
m_cachedir = user_cache_dir("shaders");
m_binaries = Global.gfx_shader_binaries;
if (m_binaries)
{
// the binaries are compiled with the constants written in the files, the sources get those of the renderer
std::vector<std::string> differences;
if (false == gfx::shader_constants_match(gfx::shader_api::vulkan14, "common.glsl", Preamble, {}, differences))
{
std::string text;
for (auto const &difference : differences)
{
text += "\n " + difference;
}
ErrorLog("vulkan: the constants in " + gfx::shader_source_dir(gfx::shader_api::vulkan14) + "common.glsl aren't those of the renderer, so the SPIR-V binaries of " +
gfx::shader_binary_dir(gfx::shader_api::vulkan14) + " can't be used; the shaders are compiled from the sources:" + text,
logtype::shader);
m_binaries = false;
}
}
log_info("shaders: " + std::string(m_binaries ? "the SPIR-V binaries of " + gfx::shader_binary_dir(gfx::shader_api::vulkan14) + " first, then " : "") + "the cache in " + m_cachedir + ", then " + m_compilerversion);
return true;
}
void shader_library::shutdown()
{
m_shaders.clear();
if (m_glslang)
{
glslang::FinalizeProcess();
m_glslang = false;
}
}
std::shared_ptr<shader_code const> shader_library::get(std::string const &Name, std::string const &Defines)
{
auto const key{Name + "|" + Defines};
auto const lookup{m_shaders.find(key)};
if (lookup != m_shaders.end())
{
return lookup->second;
}
auto code{std::make_shared<shader_code>()};
code->source = gfx::preprocess_shader(gfx::shader_api::vulkan14, Name, Defines, m_preamble, true);
code->stage = stage_bit(code->source.stage);
auto const label{Name + (code->source.variant.empty() ? "" : " (" + code->source.variant + ")")};
if (m_binaries)
{
std::string reason;
code->spirv = gfx::load_shader_binary(code->source, reason);
if (false == reason.empty())
{
log_info("shader " + label + ": " + reason + ", compiled from the source");
}
if (false == code->spirv.empty())
{
code->origin = "binary";
++m_frombinary;
}
}
if (code->spirv.empty())
{
if (false == code->source.has_source)
{
throw gfx::shader_exception("cannot read shader: " + gfx::shader_source_dir(gfx::shader_api::vulkan14) + Name);
}
std::uint64_t hash{14695981039346656037ull};
hash_bytes(hash, code->source.text.data(), code->source.text.size());
hash_bytes(hash, m_compilerversion.data(), m_compilerversion.size());
auto const cachepath{m_cachedir + Name + (code->source.variant.empty() ? "" : "." + code->source.variant) + "." + hex(hash) + ".spv"};
code->spirv = read_spirv(cachepath);
if (false == code->spirv.empty())
{
code->origin = "cache";
++m_fromcache;
}
else
{
code->spirv = compile(code->source);
code->origin = "compiled";
++m_compiled;
write_file(cachepath, code->spirv.data(), code->spirv.size() * sizeof(std::uint32_t));
}
}
// the text isn't needed any more
code->source.text = std::string();
m_shaders.emplace(key, code);
return code;
}
std::vector<std::uint32_t> shader_library::compile(gfx::shader_source const &Source) const
{
auto const language{language_of(Source.stage)};
glslang::TShader shader(language);
char const *text{Source.text.c_str()};
char const *name{Source.name.c_str()};
shader.setStringsWithLengthsAndNames(&text, nullptr, &name, 1);
shader.setEnvInput(glslang::EShSourceGlsl, language, glslang::EShClientVulkan, 100);
shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_4);
shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_6);
auto const messages{static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules)};
if (false == shader.parse(GetDefaultResources(), 460, false, messages))
{
auto const info{std::string(shader.getInfoLog())};
ErrorLog("bad shader: " + Source.name + ": " + info + "(source strings: " + files_of(Source) + ")", logtype::shader);
throw gfx::shader_exception("failed to compile " + Source.name + ": " + info);
}
glslang::TProgram program;
program.addShader(&shader);
if (false == program.link(messages))
{
auto const info{std::string(program.getInfoLog())};
ErrorLog("bad shader: " + Source.name + ": " + info, logtype::shader);
throw gfx::shader_exception("failed to link " + Source.name + ": " + info);
}
std::vector<std::uint32_t> spirv;
glslang::SpvOptions options;
options.validate = false;
glslang::GlslangToSpv(*program.getIntermediate(language), spirv, &options);
if (spirv.empty())
{
throw gfx::shader_exception("no SPIR-V made of " + Source.name);
}
return spirv;
}
std::string shader_library::info() const
{
return "shaders: " + std::to_string(m_shaders.size()) + " (" + std::to_string(m_frombinary) + " binaries, " + std::to_string(m_fromcache) + " from the cache, " + std::to_string(m_compiled) + " compiled)";
}
// pipeline library
pipeline_library::~pipeline_library()
{
shutdown();
}
bool pipeline_library::init(device const &Device, std::string &Reason)
{
m_device = &Device;
auto const cachedir{user_cache_dir("")};
m_cachepath = cachedir + "pipelines.bin";
// the cache of another device or driver is no good; vulkan would ignore it, but it would be written over anyway
std::vector<char> data;
{
std::ifstream file(m_cachepath, std::ios::binary | std::ios::ate);
if (file.is_open())
{
data.resize(static_cast<std::size_t>(file.tellg()));
file.seekg(0);
file.read(data.data(), static_cast<std::streamsize>(data.size()));
if (false == static_cast<bool>(file))
{
data.clear();
}
}
}
if (false == data.empty())
{
VkPipelineCacheHeaderVersionOne header{};
auto const &caps{Device.caps()};
bool valid{data.size() >= sizeof(header)};
if (valid)
{
std::memcpy(&header, data.data(), sizeof(header));
valid = (header.headerSize >= sizeof(header)) && (header.headerVersion == VK_PIPELINE_CACHE_HEADER_VERSION_ONE) && (header.vendorID == caps.vendor_id) && (header.deviceID == caps.device_id) &&
(std::memcmp(header.pipelineCacheUUID, caps.pipeline_cache_uuid.data(), VK_UUID_SIZE) == 0);
}
if (false == valid)
{
log_info("the pipeline cache " + m_cachepath + " comes from another device or driver, made anew");
data.clear();
}
}
VkPipelineCacheCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
info.initialDataSize = data.size();
info.pInitialData = (data.empty() ? nullptr : data.data());
if (!VK14_VERIFY(vkCreatePipelineCache(Device.handle(), &info, nullptr, &m_cache)))
{
// without the old data, then
info.initialDataSize = 0;
info.pInitialData = nullptr;
if (!VK14_VERIFY(vkCreatePipelineCache(Device.handle(), &info, nullptr, &m_cache)))
{
m_cache = VK_NULL_HANDLE;
Reason = "no pipeline cache";
return false;
}
}
log_info("pipeline cache " + m_cachepath + (data.empty() ? " (empty)" : " (" + std::to_string(data.size() / 1024) + " KiB)"));
return true;
}
void pipeline_library::shutdown()
{
if (m_device == nullptr)
{
return;
}
auto const device{m_device->handle()};
if (device != VK_NULL_HANDLE)
{
for (auto const &[key, pipeline] : m_pipelines)
{
if (pipeline != VK_NULL_HANDLE)
{
vkDestroyPipeline(device, pipeline, nullptr);
}
}
if (m_cache != VK_NULL_HANDLE)
{
std::size_t size{0};
if (VK14_VERIFY(vkGetPipelineCacheData(device, m_cache, &size, nullptr)) && (size > 0))
{
std::vector<char> data(size);
if (VK14_VERIFY(vkGetPipelineCacheData(device, m_cache, &size, data.data())))
{
write_file(m_cachepath, data.data(), size);
}
}
vkDestroyPipelineCache(device, m_cache, nullptr);
}
}
m_pipelines.clear();
m_cache = VK_NULL_HANDLE;
m_device = nullptr;
}
std::uint64_t pipeline_library::key_of(pipeline_desc const &Desc) const
{
std::uint64_t hash{14695981039346656037ull};
for (auto const &stage : Desc.stages)
{
hash_value(hash, stage.get());
}
hash_value(hash, Desc.layout);
for (auto const &binding : Desc.vertices.bindings)
{
hash_value(hash, binding.binding);
hash_value(hash, binding.stride);
hash_value(hash, binding.inputRate);
}
for (auto const &attribute : Desc.vertices.attributes)
{
hash_value(hash, attribute.location);
hash_value(hash, attribute.binding);
hash_value(hash, attribute.format);
hash_value(hash, attribute.offset);
}
hash_value(hash, Desc.topology);
hash_value(hash, Desc.blending);
for (auto const format : Desc.colour_formats)
{
hash_value(hash, format);
}
hash_value(hash, Desc.depth_format);
hash_value(hash, Desc.samples);
for (auto const value : Desc.specialization)
{
hash_value(hash, value);
}
return hash;
}
namespace
{
VkPrimitiveTopology topology_class_head(VkPrimitiveTopology const Topology)
{
return Topology;
}
std::vector<VkDynamicState> const dynamic_states{VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR,
VK_DYNAMIC_STATE_CULL_MODE,
VK_DYNAMIC_STATE_FRONT_FACE,
VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY,
VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE,
VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE,
VK_DYNAMIC_STATE_DEPTH_COMPARE_OP,
VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE,
VK_DYNAMIC_STATE_DEPTH_BIAS,
VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE,
VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE};
} // namespace
VkPipeline pipeline_library::get(pipeline_desc const &Desc)
{
auto const key{key_of(Desc)};
auto const lookup{m_pipelines.find(key)};
if (lookup != m_pipelines.end())
{
return lookup->second;
}
auto const start{std::chrono::steady_clock::now()};
// the code goes straight to the stages (maintenance5), with the specialization of all of them
std::vector<VkSpecializationMapEntry> entries(Desc.specialization.size());
for (std::size_t i = 0; i < entries.size(); ++i)
{
entries[i] = {static_cast<std::uint32_t>(i), static_cast<std::uint32_t>(i * sizeof(std::uint32_t)), sizeof(std::uint32_t)};
}
VkSpecializationInfo specialization{};
specialization.mapEntryCount = static_cast<std::uint32_t>(entries.size());
specialization.pMapEntries = entries.data();
specialization.dataSize = Desc.specialization.size() * sizeof(std::uint32_t);
specialization.pData = Desc.specialization.data();
std::vector<VkShaderModuleCreateInfo> modules(Desc.stages.size());
std::vector<VkPipelineShaderStageCreateInfo> stages(Desc.stages.size());
for (std::size_t i = 0; i < Desc.stages.size(); ++i)
{
modules[i].sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
modules[i].codeSize = Desc.stages[i]->spirv.size() * sizeof(std::uint32_t);
modules[i].pCode = Desc.stages[i]->spirv.data();
stages[i].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stages[i].pNext = &modules[i];
stages[i].stage = Desc.stages[i]->stage;
stages[i].module = VK_NULL_HANDLE;
stages[i].pName = "main";
stages[i].pSpecializationInfo = (entries.empty() ? nullptr : &specialization);
}
VkPipelineVertexInputStateCreateInfo vertexinput{};
vertexinput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexinput.vertexBindingDescriptionCount = static_cast<std::uint32_t>(Desc.vertices.bindings.size());
vertexinput.pVertexBindingDescriptions = Desc.vertices.bindings.data();
vertexinput.vertexAttributeDescriptionCount = static_cast<std::uint32_t>(Desc.vertices.attributes.size());
vertexinput.pVertexAttributeDescriptions = Desc.vertices.attributes.data();
VkPipelineInputAssemblyStateCreateInfo inputassembly{};
inputassembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputassembly.topology = topology_class_head(Desc.topology);
VkPipelineViewportStateCreateInfo viewport{};
viewport.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewport.viewportCount = 1;
viewport.scissorCount = 1;
VkPipelineRasterizationStateCreateInfo rasterization{};
rasterization.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterization.polygonMode = VK_POLYGON_MODE_FILL;
rasterization.cullMode = VK_CULL_MODE_NONE;
rasterization.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
rasterization.lineWidth = 1.f;
VkPipelineMultisampleStateCreateInfo multisample{};
multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisample.rasterizationSamples = Desc.samples;
VkPipelineDepthStencilStateCreateInfo depthstencil{};
depthstencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depthstencil.depthCompareOp = VK_COMPARE_OP_GREATER_OR_EQUAL;
depthstencil.maxDepthBounds = 1.f;
std::vector<VkPipelineColorBlendAttachmentState> attachments(Desc.colour_formats.size());
for (auto &attachment : attachments)
{
attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
switch (Desc.blending)
{
case blend_mode::none:
break;
case blend_mode::alpha:
attachment.blendEnable = VK_TRUE;
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
attachment.colorBlendOp = VK_BLEND_OP_ADD;
attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
break;
case blend_mode::premultiplied:
attachment.blendEnable = VK_TRUE;
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
attachment.colorBlendOp = VK_BLEND_OP_ADD;
attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
break;
case blend_mode::additive:
attachment.blendEnable = VK_TRUE;
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.colorBlendOp = VK_BLEND_OP_ADD;
attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
break;
}
}
VkPipelineColorBlendStateCreateInfo blend{};
blend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
blend.attachmentCount = static_cast<std::uint32_t>(attachments.size());
blend.pAttachments = attachments.data();
VkPipelineDynamicStateCreateInfo dynamic{};
dynamic.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamic.dynamicStateCount = static_cast<std::uint32_t>(dynamic_states.size());
dynamic.pDynamicStates = dynamic_states.data();
// no render pass: the formats of the attachments only (dynamic rendering)
VkPipelineRenderingCreateInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
rendering.colorAttachmentCount = static_cast<std::uint32_t>(Desc.colour_formats.size());
rendering.pColorAttachmentFormats = Desc.colour_formats.data();
rendering.depthAttachmentFormat = Desc.depth_format;
VkGraphicsPipelineCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
info.pNext = &rendering;
info.stageCount = static_cast<std::uint32_t>(stages.size());
info.pStages = stages.data();
info.pVertexInputState = &vertexinput;
info.pInputAssemblyState = &inputassembly;
info.pViewportState = &viewport;
info.pRasterizationState = &rasterization;
info.pMultisampleState = &multisample;
info.pDepthStencilState = &depthstencil;
info.pColorBlendState = &blend;
info.pDynamicState = &dynamic;
info.layout = Desc.layout;
VkPipeline pipeline{VK_NULL_HANDLE};
if (!VK14_VERIFY(vkCreateGraphicsPipelines(m_device->handle(), m_cache, 1, &info, nullptr, &pipeline)))
{
log_error("the pipeline " + Desc.name + " can't be made");
pipeline = VK_NULL_HANDLE;
}
else
{
name_object(m_device->handle(), VK_OBJECT_TYPE_PIPELINE, handle_value(pipeline), Desc.name);
}
m_creationms += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
m_pipelines.emplace(key, pipeline);
return pipeline;
}
VkPipeline pipeline_library::compute(std::shared_ptr<shader_code const> const &Shader, VkPipelineLayout Layout, std::vector<std::uint32_t> const &Specialization, std::string const &Name)
{
std::uint64_t key{14695981039346656037ull};
hash_value(key, Shader.get());
hash_value(key, Layout);
for (auto const value : Specialization)
{
hash_value(key, value);
}
auto const lookup{m_pipelines.find(key)};
if (lookup != m_pipelines.end())
{
return lookup->second;
}
auto const start{std::chrono::steady_clock::now()};
std::vector<VkSpecializationMapEntry> entries(Specialization.size());
for (std::size_t i = 0; i < entries.size(); ++i)
{
entries[i] = {static_cast<std::uint32_t>(i), static_cast<std::uint32_t>(i * sizeof(std::uint32_t)), sizeof(std::uint32_t)};
}
VkSpecializationInfo specialization{};
specialization.mapEntryCount = static_cast<std::uint32_t>(entries.size());
specialization.pMapEntries = entries.data();
specialization.dataSize = Specialization.size() * sizeof(std::uint32_t);
specialization.pData = Specialization.data();
VkShaderModuleCreateInfo module{};
module.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
module.codeSize = Shader->spirv.size() * sizeof(std::uint32_t);
module.pCode = Shader->spirv.data();
VkComputePipelineCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
info.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
info.stage.pNext = &module;
info.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
info.stage.pName = "main";
info.stage.pSpecializationInfo = (entries.empty() ? nullptr : &specialization);
info.layout = Layout;
VkPipeline pipeline{VK_NULL_HANDLE};
if (!VK14_VERIFY(vkCreateComputePipelines(m_device->handle(), m_cache, 1, &info, nullptr, &pipeline)))
{
log_error("the compute pipeline " + Name + " can't be made");
pipeline = VK_NULL_HANDLE;
}
else
{
name_object(m_device->handle(), VK_OBJECT_TYPE_PIPELINE, handle_value(pipeline), Name);
}
m_creationms += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
m_pipelines.emplace(key, pipeline);
return pipeline;
}
std::string pipeline_library::info() const
{
char buffer[32] = {};
std::snprintf(buffer, sizeof(buffer), "%.1f", m_creationms);
return "pipelines: " + std::to_string(m_pipelines.size()) + " (made in " + buffer + " ms)";
}
void set_draw_state(VkCommandBuffer Commands, VkExtent2D Extent)
{
VkViewport viewport{0.f, 0.f, static_cast<float>(Extent.width), static_cast<float>(Extent.height), 0.f, 1.f};
vkCmdSetViewport(Commands, 0, 1, &viewport);
VkRect2D scissor{{0, 0}, Extent};
vkCmdSetScissor(Commands, 0, 1, &scissor);
vkCmdSetCullMode(Commands, VK_CULL_MODE_NONE);
vkCmdSetFrontFace(Commands, VK_FRONT_FACE_COUNTER_CLOCKWISE);
vkCmdSetPrimitiveTopology(Commands, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
vkCmdSetDepthTestEnable(Commands, VK_FALSE);
vkCmdSetDepthWriteEnable(Commands, VK_FALSE);
// reverse z, as the opengl renderer
vkCmdSetDepthCompareOp(Commands, VK_COMPARE_OP_GREATER_OR_EQUAL);
vkCmdSetDepthBiasEnable(Commands, VK_FALSE);
vkCmdSetDepthBias(Commands, 0.f, 0.f, 0.f);
vkCmdSetPrimitiveRestartEnable(Commands, VK_FALSE);
vkCmdSetStencilTestEnable(Commands, VK_FALSE);
}
} // namespace vk14

View File

@@ -0,0 +1,134 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the shaders and the pipelines of the vulkan renderer.
//
// a shader of shaders/vk14 is taken from the SPIR-V binary of shaders/vk14bin when there's an up to date one
// (gfx.shaders.binaries), from the cache of the shaders compiled before (cache/vulkan14/shaders in the directory of the user,
// keyed by the source with its includes, the variant and the version of glslang), or compiled now by glslang and put in the
// cache. the pipelines are made from a description of what isn't dynamic (the shaders, the layout of the vertices, the class
// of the primitives, the blending, the formats of the attachments, the samples); the rest of the state is dynamic. the
// VkPipelineCache is read from and written to the directory of the user, and thrown away when it comes from another device
// or driver. no VkShaderModule: the code goes to the stages of the pipelines (maintenance5)
#pragma once
#include "rendering/vulkan14/vulkan14common.h"
#include "rendering/common/shadersource.h"
#include <memory>
#include <unordered_map>
namespace vk14
{
class device;
// the SPIR-V of a shader, and what its source says (the material directives)
struct shader_code
{
gfx::shader_source source;
VkShaderStageFlagBits stage{VK_SHADER_STAGE_VERTEX_BIT};
std::vector<std::uint32_t> spirv;
// where it came from: "binary", "cache", "compiled"
char const *origin{""};
};
class shader_library
{
public:
shader_library() = default;
shader_library(shader_library const &) = delete;
shader_library &operator=(shader_library const &) = delete;
~shader_library();
// Preamble: what the renderer puts after #version (the constants of vulkan14bindings.h)
bool init(std::string const &Preamble, std::string &Reason);
void shutdown();
// the shader of shaders/vk14 (Defines: the variant). throws gfx::shader_exception
std::shared_ptr<shader_code const> get(std::string const &Name, std::string const &Defines = {});
// what was loaded from where, for the debug panel
std::string info() const;
private:
std::vector<std::uint32_t> compile(gfx::shader_source const &Source) const;
std::string m_preamble;
std::string m_cachedir;
std::string m_compilerversion;
bool m_glslang{false};
bool m_binaries{false};
std::unordered_map<std::string, std::shared_ptr<shader_code const>> m_shaders;
int m_frombinary{0};
int m_fromcache{0};
int m_compiled{0};
};
// how the colour of a pipeline is mixed with what is there
enum class blend_mode
{
none,
alpha, // the colour over what is there by its alpha, the alpha kept for what's behind (the user interface)
premultiplied,
additive
};
struct vertex_layout
{
std::vector<VkVertexInputBindingDescription> bindings;
std::vector<VkVertexInputAttributeDescription> attributes;
};
// what a pipeline is made of; the rest of the state is set while drawing (vk14::set_draw_state())
struct pipeline_desc
{
std::vector<std::shared_ptr<shader_code const>> stages;
VkPipelineLayout layout{VK_NULL_HANDLE};
vertex_layout vertices;
// the class of the primitives; the primitive itself is dynamic
VkPrimitiveTopology topology{VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST};
blend_mode blending{blend_mode::none};
std::vector<VkFormat> colour_formats;
VkFormat depth_format{VK_FORMAT_UNDEFINED};
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
// the values of the specialization constants 0, 1, 2 ... of all the stages
std::vector<std::uint32_t> specialization;
std::string name;
};
class pipeline_library
{
public:
pipeline_library() = default;
pipeline_library(pipeline_library const &) = delete;
pipeline_library &operator=(pipeline_library const &) = delete;
~pipeline_library();
bool init(device const &Device, std::string &Reason);
// writes the pipeline cache to the disk and destroys the pipelines
void shutdown();
// the pipeline of the description, made at the first call (from the cache on the disk, when it has it). null when it can't be made
VkPipeline get(pipeline_desc const &Desc);
VkPipeline compute(std::shared_ptr<shader_code const> const &Shader, VkPipelineLayout Layout, std::vector<std::uint32_t> const &Specialization = {}, std::string const &Name = {});
std::string info() const;
private:
std::uint64_t key_of(pipeline_desc const &Desc) const;
device const *m_device{nullptr};
VkPipelineCache m_cache{VK_NULL_HANDLE};
std::string m_cachepath;
std::unordered_map<std::uint64_t, VkPipeline> m_pipelines;
double m_creationms{0.0};
};
// the dynamic state of a pipeline in its plain form: the whole of Extent, no culling, no depth, triangles
void set_draw_state(VkCommandBuffer Commands, VkExtent2D Extent);
} // namespace vk14

View File

@@ -280,6 +280,7 @@ bool swapchain::build(std::string &Reason)
vkGetSwapchainImagesKHR(device, m_swapchain, &count, nullptr);
m_images.resize(count);
vkGetSwapchainImagesKHR(device, m_swapchain, &count, m_images.data());
m_states.assign(count, image_state{});
VkSemaphoreCreateInfo semaphoreinfo{};
semaphoreinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
@@ -324,6 +325,15 @@ swapchain::acquire_result swapchain::acquire(std::uint32_t const Frame)
}
auto const result{vkAcquireNextImageKHR(m_device->handle(), m_swapchain, UINT64_MAX, m_acquiresemaphores[Frame], VK_NULL_HANDLE, &m_image)};
if ((result == VK_SUCCESS) || (result == VK_SUBOPTIMAL_KHR))
{
// what the image held is of no use; its use waits for the acquire semaphore, which the submit waits for at the colour output
if (m_states.size() < m_images.size())
{
m_states.resize(m_images.size());
}
m_states[m_image] = image_state{VK_IMAGE_LAYOUT_UNDEFINED, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_NONE};
}
switch (result)
{
case VK_SUCCESS:
@@ -422,6 +432,7 @@ void swapchain::release_images()
m_views.clear();
m_presentsemaphores.clear();
m_images.clear();
m_states.clear();
}
void swapchain::destroy()

View File

@@ -14,6 +14,7 @@ http://mozilla.org/MPL/2.0/.
#pragma once
#include "rendering/vulkan14/vulkan14common.h"
#include "rendering/vulkan14/vulkan14passes.h"
namespace vk14
{
@@ -44,6 +45,20 @@ class swapchain
acquire_result acquire(std::uint32_t const Frame);
// the image acquired, from undefined to cleared with Colour (linear) and ready to be shown
void record_clear(VkCommandBuffer Commands, glm::vec4 const &Colour) const;
// the image acquired and its view
VkImage image() const
{
return m_images[m_image];
}
VkImageView view() const
{
return m_views[m_image];
}
// a use of the image acquired by a pass (vk14::pass_list); the image starts the frame undefined, after the acquire semaphore
image_use use(VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access)
{
return image_use{m_images[m_image], VK_IMAGE_ASPECT_COLOR_BIT, &m_states[m_image], Layout, Stage, Access};
}
// the semaphore signalled when the image of the frame Frame is acquired
VkSemaphore acquire_semaphore(std::uint32_t const Frame) const
{
@@ -111,6 +126,8 @@ class swapchain
bool m_rebuild{false};
std::vector<VkImage> m_images;
// the states of the images for the barriers of the passes
std::vector<image_state> m_states;
std::vector<VkImageView> m_views;
// one per image: a semaphore given to the presentation can be reused only once the image comes back
std::vector<VkSemaphore> m_presentsemaphores;

View File

@@ -0,0 +1,596 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14texture.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include <cstring>
namespace vk14
{
// uploader
void uploader::init(allocator const &Allocator, deletion_queue &Deletions, VkPhysicalDevice Physical)
{
m_allocator = &Allocator;
m_deletions = &Deletions;
m_physical = Physical;
}
bool uploader::can_blit(VkFormat Format) const
{
auto const lookup{m_blittable.find(Format)};
if (lookup != m_blittable.end())
{
return lookup->second;
}
VkFormatProperties properties{};
vkGetPhysicalDeviceFormatProperties(m_physical, Format, &properties);
auto const needed{VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT};
auto const result{(properties.optimalTilingFeatures & needed) == needed};
m_blittable.emplace(Format, result);
return result;
}
void uploader::begin_frame(VkCommandBuffer Commands, std::uint64_t Value)
{
m_commands = Commands;
m_value = Value;
m_recorded = false;
auto pending{std::move(m_pending)};
m_pending.clear();
for (auto &job : pending)
{
record(job);
}
}
bool uploader::end_frame()
{
m_commands = VK_NULL_HANDLE;
return m_recorded;
}
bool uploader::upload(std::shared_ptr<image> const &Image, void const *Data, VkDeviceSize Size, std::vector<level> const &Levels, bool const Mipmaps)
{
job upload;
upload.target = Image;
upload.levels = Levels;
upload.mipmaps = Mipmaps;
upload.staging = std::make_shared<buffer>();
if ((false == upload.staging->create(*m_allocator, Size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, memory_kind::upload)) || (upload.staging->mapped() == nullptr))
{
log_error("no staging buffer of " + memory_size_string(Size) + " for an upload");
return false;
}
std::memcpy(upload.staging->mapped(), Data, static_cast<std::size_t>(Size));
upload.staging->flush(0, Size);
m_uploaded += Size;
if (m_commands != VK_NULL_HANDLE)
{
record(upload);
}
else
{
m_pending.emplace_back(std::move(upload));
}
return true;
}
void uploader::record(job &Job)
{
auto &target{*Job.target};
auto const commands{m_commands};
auto const aspect{target.aspect()};
auto const layers{target.desc().layers};
auto const mips{target.desc().mips};
transition(commands, target, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
std::vector<VkBufferImageCopy2> regions;
for (std::size_t i = 0; i < Job.levels.size(); ++i)
{
VkBufferImageCopy2 region{};
region.sType = VK_STRUCTURE_TYPE_BUFFER_IMAGE_COPY_2;
region.bufferOffset = Job.levels[i].offset;
region.imageSubresource = {aspect, static_cast<std::uint32_t>(i), 0, layers};
region.imageExtent = Job.levels[i].extent;
regions.push_back(region);
}
VkCopyBufferToImageInfo2 copy{};
copy.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_TO_IMAGE_INFO_2;
copy.srcBuffer = Job.staging->handle();
copy.dstImage = target.handle();
copy.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
copy.regionCount = static_cast<std::uint32_t>(regions.size());
copy.pRegions = regions.data();
vkCmdCopyBufferToImage2(commands, &copy);
auto const given{static_cast<std::uint32_t>(Job.levels.size())};
if (Job.mipmaps && (mips > given) && (given > 0))
{
// the other levels made by blits, each from the one before it
auto barrier = [&](std::uint32_t Level, std::uint32_t Count, VkImageLayout From, VkImageLayout To, VkPipelineStageFlags2 SrcStage, VkAccessFlags2 SrcAccess, VkPipelineStageFlags2 DstStage, VkAccessFlags2 DstAccess) {
VkImageMemoryBarrier2 entry{};
entry.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
entry.srcStageMask = SrcStage;
entry.srcAccessMask = SrcAccess;
entry.dstStageMask = DstStage;
entry.dstAccessMask = DstAccess;
entry.oldLayout = From;
entry.newLayout = To;
entry.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
entry.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
entry.image = target.handle();
entry.subresourceRange = {aspect, Level, Count, 0, layers};
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.imageMemoryBarrierCount = 1;
dependency.pImageMemoryBarriers = &entry;
vkCmdPipelineBarrier2(commands, &dependency);
};
auto const extent{target.desc().extent};
for (std::uint32_t level = given; level < mips; ++level)
{
barrier(level - 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_BLIT_BIT,
VK_ACCESS_2_TRANSFER_READ_BIT);
VkImageBlit2 blit{};
blit.sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2;
blit.srcSubresource = {aspect, level - 1, 0, layers};
blit.srcOffsets[1] = {static_cast<std::int32_t>(std::max(extent.width >> (level - 1), 1u)), static_cast<std::int32_t>(std::max(extent.height >> (level - 1), 1u)), 1};
blit.dstSubresource = {aspect, level, 0, layers};
blit.dstOffsets[1] = {static_cast<std::int32_t>(std::max(extent.width >> level, 1u)), static_cast<std::int32_t>(std::max(extent.height >> level, 1u)), 1};
VkBlitImageInfo2 info{};
info.sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2;
info.srcImage = target.handle();
info.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
info.dstImage = target.handle();
info.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
info.regionCount = 1;
info.pRegions = &blit;
info.filter = VK_FILTER_LINEAR;
vkCmdBlitImage2(commands, &info);
}
barrier(0, mips - 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_NONE, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
barrier(mips - 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
target.state = image_state{VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT};
}
else
{
transition(commands, target, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
}
m_recorded = true;
// the staging buffer goes, and the image may, once the gpu is done with the frame
m_deletions->push(m_value, [staging = Job.staging, image = Job.target]() {});
}
// image table
std::uint64_t image_table::add(std::shared_ptr<image> const &Image, VkSampler Sampler)
{
std::uint64_t id;
if (false == m_free.empty())
{
id = m_free.back();
m_free.pop_back();
}
else
{
id = m_entries.size();
m_entries.emplace_back();
}
m_entries[id] = entry{Image, Sampler};
return id;
}
void image_table::replace(std::uint64_t Id, std::shared_ptr<image> const &Image, VkSampler Sampler)
{
if ((Id > 0) && (Id < m_entries.size()))
{
m_entries[Id] = entry{Image, Sampler};
}
}
void image_table::remove(std::uint64_t Id, deletion_queue &Deletions, std::uint64_t Value)
{
if ((Id == 0) || (Id >= m_entries.size()) || (m_entries[Id].target == nullptr))
{
return;
}
Deletions.push(Value, [image = std::move(m_entries[Id].target)]() {});
m_entries[Id] = entry{};
m_free.push_back(Id);
}
image_table::entry const *image_table::find(std::uint64_t Id) const
{
if ((Id == 0) || (Id >= m_entries.size()) || (m_entries[Id].target == nullptr))
{
return nullptr;
}
return &m_entries[Id];
}
void image_table::clear()
{
m_entries.assign(1, entry{});
m_free.clear();
}
// formats
texture_format texture_format_of(GLint Format, GLint Hint)
{
// (etc2 of the ktx files isn't mapped: the desktop gpus don't sample it, and the feature isn't enabled on the device; the
// opengl driver decodes it)
// the material wants srgb colours (the default), or values: the compressed formats keep their alpha either way, as in the
// opengl renderer (opengl_texture::mapping)
auto const srgb{(Hint == 0) || (Hint == GL_SRGB_ALPHA) || (Hint == GL_SRGB)};
texture_format result;
switch (Format)
{
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
result.format = ((srgb || (Format == GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT)) ? VK_FORMAT_BC1_RGBA_SRGB_BLOCK : VK_FORMAT_BC1_RGBA_UNORM_BLOCK);
result.compressed = true;
result.block_bytes = 8;
return result;
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
result.format = ((srgb || (Format == GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT)) ? VK_FORMAT_BC2_SRGB_BLOCK : VK_FORMAT_BC2_UNORM_BLOCK);
result.compressed = true;
result.block_bytes = 16;
return result;
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
result.format = ((srgb || (Format == GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT)) ? VK_FORMAT_BC3_SRGB_BLOCK : VK_FORMAT_BC3_UNORM_BLOCK);
result.compressed = true;
result.block_bytes = 16;
return result;
case GL_RGBA:
case GL_BGRA:
case GL_RGB:
result.format = (Format == GL_BGRA ? (srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM) : (srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM));
// rgb texels aren't sampled by every gpu, they are made rgba
result.expand_rgb = (Format == GL_RGB);
break;
default:
return result;
}
// what the material takes of the colour, as the internal formats of opengl give it (GL_SRGB8, GL_RG8, GL_R8: the missing
// channels read 0, the alpha 1)
switch (Hint)
{
case GL_SRGB:
case GL_RGB:
result.components.a = VK_COMPONENT_SWIZZLE_ONE;
break;
case GL_RG:
result.components.b = VK_COMPONENT_SWIZZLE_ZERO;
result.components.a = VK_COMPONENT_SWIZZLE_ONE;
break;
case GL_RED:
result.components.g = VK_COMPONENT_SWIZZLE_ZERO;
result.components.b = VK_COMPONENT_SWIZZLE_ZERO;
result.components.a = VK_COMPONENT_SWIZZLE_ONE;
break;
default:
break;
}
return result;
}
// texture
vulkan14_texture::~vulkan14_texture()
{
if (m_resources.pending != nullptr)
{
std::lock_guard<std::mutex> lock(m_resources.pending->mutex);
auto &list{m_resources.pending->textures};
list.erase(std::remove(list.begin(), list.end(), this), list.end());
}
}
bool vulkan14_texture::create(bool const Static)
{
if ((data_state == resource_state::none) && (true == reload_on_use))
{
// the data was dropped along with the gpu image when the texture was far away
reload_on_use = false;
load();
}
if ((data_state == resource_state::loading) && (request != nullptr))
{
complete_data();
}
if (m_made)
{
return true;
}
if (data_state != resource_state::good)
{
// don't bother until we have useful texture data
return false;
}
auto const format{texture_format_of(data_format, components_hint != 0 ? components_hint : GL_SRGB_ALPHA)};
VkFormatProperties properties{};
if (format.format != VK_FORMAT_UNDEFINED)
{
vkGetPhysicalDeviceFormatProperties(m_resources.gpu->physical(), format.format, &properties);
}
if ((format.format == VK_FORMAT_UNDEFINED) || ((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) == 0))
{
ErrorLog("Bad texture: the format of \"" + name + "\" can't be sampled by the gpu", logtype::texture);
on_failed();
is_ready = true;
return true;
}
std::vector<uploader::level> levels;
std::vector<std::uint8_t> expanded;
std::uint8_t const *source{data.data()};
VkDeviceSize size{data.size()};
auto width{data_width}, height{data_height};
if (format.compressed)
{
VkDeviceSize offset{0};
for (unsigned int level = 0; level < data_mapcount; ++level)
{
auto const levelsize{static_cast<VkDeviceSize>((std::max(width, 4u) + 3) / 4) * ((std::max(height, 4u) + 3) / 4) * format.block_bytes};
if (offset + levelsize > data.size())
{
break;
}
levels.push_back({offset, {width, height, 1}});
offset += levelsize;
width = std::max(width / 2, 1u);
height = std::max(height / 2, 1u);
}
size = offset;
}
else
{
if (format.expand_rgb)
{
auto const pixels{static_cast<std::size_t>(data_width) * data_height};
expanded.resize(pixels * 4);
for (std::size_t i = 0; i < pixels; ++i)
{
expanded[i * 4 + 0] = data[i * 3 + 0];
expanded[i * 4 + 1] = data[i * 3 + 1];
expanded[i * 4 + 2] = data[i * 3 + 2];
expanded[i * 4 + 3] = 0xff;
}
source = expanded.data();
size = expanded.size();
}
else
{
size = static_cast<VkDeviceSize>(data_width) * data_height * 4;
}
levels.push_back({0, {data_width, data_height, 1}});
}
auto const available{expanded.empty() ? data.size() : expanded.size()};
if (levels.empty() || (size > available) || (data_width == 0) || (data_height == 0))
{
ErrorLog("Bad texture: \"" + name + "\" holds less data than its size needs", logtype::texture);
on_failed();
is_ready = true;
return true;
}
// a single level is made into the whole chain on the gpu, as glGenerateMipmap() does for the opengl renderer
auto const generate{(levels.size() == 1) && (false == format.compressed) && m_resources.uploads->can_blit(format.format)};
image_desc desc;
desc.format = format.format;
desc.extent = {data_width, data_height, 1};
desc.mips = (generate ? static_cast<std::uint32_t>(std::floor(std::log2(std::max(data_width, data_height)))) + 1 : static_cast<std::uint32_t>(levels.size()));
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | (generate ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0);
desc.components = format.components;
auto image{std::make_shared<vk14::image>()};
if (false == image->create(*m_resources.memory, desc, name))
{
ErrorLog("Bad texture: no gpu image for \"" + name + "\"", logtype::texture);
on_failed();
is_ready = true;
return true;
}
if (false == m_resources.uploads->upload(image, source, size, levels, generate))
{
// never filled, so never sampled
ErrorLog("Bad texture: \"" + name + "\" couldn't be uploaded", logtype::texture);
on_failed();
is_ready = true;
return true;
}
// the traits of the file: clamped along s or t
auto const clamped{(traits.find('s') != std::string::npos) || (traits.find('t') != std::string::npos)};
m_image = image;
m_id = m_resources.images->add(image, clamped ? m_resources.sampling->clamp_aniso : m_resources.sampling->repeat_aniso);
m_made = true;
if ((true == Global.ResourceMove) || (false == Global.ResourceSweep) || (true == streamed()))
{
// if garbage collection is disabled we don't expect having to upload the texture more than once
data = std::vector<unsigned char>();
data_state = resource_state::none;
}
if (type == "make:")
{
// for generated textures send a request to have the actual content of the texture generated
make_request();
}
is_static = Static;
is_ready = true;
return true;
}
void vulkan14_texture::release()
{
if (false == m_made)
{
return;
}
if (is_static)
{
return;
}
if (true == streamed())
{
// no copy of the data is kept, it's read from the file again when the texture is needed (prefetch(), create())
data = std::vector<unsigned char>();
data_state = resource_state::none;
reload_on_use = true;
}
else if (data_state != resource_state::good)
{
if (type == "make:")
{
// auto generated textures only store a stub
make_stub();
}
else if (type != "internalsrc:")
{
// the data went after the upload (gfx.resource.move): read from the file again, instead of from the gpu
reload_on_use = true;
}
}
m_resources.images->remove(m_id, *m_resources.deletions, m_resources.uploads->current_value());
m_image.reset();
m_id = 0;
m_made = false;
is_ready = false;
}
void vulkan14_texture::destroy()
{
if (m_id != 0)
{
m_resources.images->remove(m_id, *m_resources.deletions, m_resources.uploads->current_value());
}
m_image.reset();
m_id = 0;
m_made = false;
}
void vulkan14_texture::on_failed()
{
// the texture counts as made, without an image (the id 0), as with the opengl renderer
if (m_id != 0)
{
m_resources.images->remove(m_id, *m_resources.deletions, m_resources.uploads->current_value());
}
m_image.reset();
m_id = 0;
m_made = true;
}
void vulkan14_texture::update_from_memory(size_t width, size_t height, const uint8_t *raw)
{
if (std::this_thread::get_id() != m_resources.thread)
{
// the renderer takes it over in its frame (apply_pending())
std::lock_guard<std::mutex> lock(m_pendingmutex);
m_pending.assign(raw, raw + width * height * 4);
m_pendingwidth = width;
m_pendingheight = height;
if (m_resources.pending != nullptr)
{
std::lock_guard<std::mutex> listlock(m_resources.pending->mutex);
auto &list{m_resources.pending->textures};
if (std::find(list.begin(), list.end(), this) == list.end())
{
list.push_back(this);
}
}
return;
}
upload_rgba(raw, width, height);
}
void vulkan14_texture::apply_pending()
{
std::vector<std::uint8_t> pending;
std::size_t width{0}, height{0};
{
std::lock_guard<std::mutex> lock(m_pendingmutex);
pending.swap(m_pending);
width = m_pendingwidth;
height = m_pendingheight;
}
if (false == pending.empty())
{
upload_rgba(pending.data(), width, height);
}
}
void vulkan14_texture::upload_rgba(std::uint8_t const *Data, std::size_t Width, std::size_t Height)
{
// as the opengl renderer: srgb rgba with mipmaps, the image made anew when the size changes
auto const width{static_cast<std::uint32_t>(Width)}, height{static_cast<std::uint32_t>(Height)};
auto const format{VK_FORMAT_R8G8B8A8_SRGB};
auto const generate{m_resources.uploads->can_blit(format)};
auto target{m_image};
auto const fresh{(m_image == nullptr) || (m_image->desc().extent.width != width) || (m_image->desc().extent.height != height) || (m_image->desc().format != format)};
if (fresh)
{
image_desc desc;
desc.format = format;
desc.extent = {width, height, 1};
desc.mips = (generate ? static_cast<std::uint32_t>(std::floor(std::log2(std::max(width, height)))) + 1 : 1);
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | (generate ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0);
target = std::make_shared<vk14::image>();
if (false == target->create(*m_resources.memory, desc, name))
{
return;
}
}
// a new image goes to the table only once it has its content
if (false == m_resources.uploads->upload(target, Data, static_cast<VkDeviceSize>(width) * height * 4, {{0, {width, height, 1}}}, generate))
{
return;
}
if (fresh)
{
auto const sampler{m_resources.sampling->repeat_aniso};
if (m_id != 0)
{
// the old image goes once the frames which drew it are done
m_resources.deletions->push(m_resources.uploads->current_value(), [old = m_image]() {});
m_resources.images->replace(m_id, target, sampler);
}
else
{
m_id = m_resources.images->add(target, sampler);
}
m_image = target;
}
data_width = width;
data_height = height;
data_format = GL_SRGB8_ALPHA8;
data_components = GL_RGBA;
m_made = true;
// the content given straight to the gpu can't be read again: released by the garbage sweep, the texture would come back as
// the grey stub
if (type == "internalsrc:")
{
is_static = true;
is_ready = true;
}
}
} // namespace vk14

View File

@@ -0,0 +1,187 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the images the vulkan renderer samples: the textures of the files (texture_source, decoded as for the opengl renderer and
// laid out the same way in the memory, decision K8 of the plan), the images of the user interface, and their uploads.
//
// the uploads are copies through staging buffers recorded into the command buffer of uploads of the frame being made, which goes
// to the queue before the commands of the frame in the same submit; an upload asked for between the frames waits for the next
// one. every image has an id in one table (decision K10): ITexture::get_id() of the textures, the ids of the images of the user
// interface and the font of imgui are all ImTextureID, so the user interface draws any of them
#pragma once
#include "rendering/vulkan14/vulkan14memory.h"
#include "rendering/vulkan14/vulkan14descriptors.h"
#include "model/Texture.h"
#include <memory>
#include <mutex>
#include <thread>
namespace vk14
{
class device;
// the uploads of data to the images
class uploader
{
public:
struct level
{
VkDeviceSize offset{0}; // in the data
VkExtent3D extent{1, 1, 1};
};
void init(allocator const &Allocator, deletion_queue &Deletions, VkPhysicalDevice Physical);
// the commands of the uploads go to Commands until end_frame(); Value: the value of the timeline the gpu reaches when it's done
// with the frame. the uploads waiting from between the frames are recorded first
void begin_frame(VkCommandBuffer Commands, std::uint64_t Value);
// whether anything was recorded to the commands of the frame
bool end_frame();
// the last value of the timeline the gpu may use the objects with: the frame being made, or the last one sent
std::uint64_t current_value() const
{
return m_value;
}
// copies Data into Image (the levels given, the others made by blits of the first one when Mipmaps), the image left ready to be
// sampled. Data is copied away. false: no staging memory, nothing will be copied
bool upload(std::shared_ptr<image> const &Image, void const *Data, VkDeviceSize Size, std::vector<level> const &Levels, bool const Mipmaps);
// the bytes uploaded since the start, for the debug panel
std::uint64_t uploaded() const
{
return m_uploaded;
}
// whether the format can be filtered by blits (the mipmaps made on the gpu)
bool can_blit(VkFormat Format) const;
private:
struct job
{
std::shared_ptr<image> target;
std::shared_ptr<buffer> staging;
std::vector<level> levels;
bool mipmaps{false};
};
void record(job &Job);
allocator const *m_allocator{nullptr};
deletion_queue *m_deletions{nullptr};
VkPhysicalDevice m_physical{VK_NULL_HANDLE};
VkCommandBuffer m_commands{VK_NULL_HANDLE};
std::uint64_t m_value{0};
bool m_recorded{false};
std::vector<job> m_pending;
std::uint64_t m_uploaded{0};
mutable std::unordered_map<VkFormat, bool> m_blittable;
};
// the images the shaders can be given by an id
class image_table
{
public:
struct entry
{
std::shared_ptr<image> target;
VkSampler sampler{VK_NULL_HANDLE};
};
// a new id for the image (0 is no image)
std::uint64_t add(std::shared_ptr<image> const &Image, VkSampler Sampler);
void replace(std::uint64_t Id, std::shared_ptr<image> const &Image, VkSampler Sampler);
// the id is free again, the image goes once the gpu is done with it
void remove(std::uint64_t Id, deletion_queue &Deletions, std::uint64_t Value);
entry const *find(std::uint64_t Id) const;
void clear();
std::size_t size() const
{
return m_entries.size() - m_free.size();
}
private:
std::vector<entry> m_entries{entry{}};
std::vector<std::uint64_t> m_free;
};
struct vulkan14_texture;
// the textures given content by other threads, waiting for the thread of the renderer
struct pending_textures
{
std::mutex mutex;
std::vector<vulkan14_texture *> textures;
};
// what the images of the renderer need from it
struct resources
{
device const *gpu{nullptr};
allocator *memory{nullptr};
uploader *uploads{nullptr};
deletion_queue *deletions{nullptr};
samplers const *sampling{nullptr};
image_table *images{nullptr};
// the thread of the renderer: the content given by other threads waits for it (update_from_memory())
std::thread::id thread;
pending_textures *pending{nullptr};
};
// the vulkan format of the data of a texture and how the data has to be changed for it
struct texture_format
{
VkFormat format{VK_FORMAT_UNDEFINED};
bool expand_rgb{false}; // rgb texels made rgba
bool compressed{false};
int block_bytes{0}; // of a block of 4x4 texels, for the compressed formats
// the channels the material reads, as the internal format of opengl would leave them
VkComponentMapping components{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY};
};
// Format: data_format of a texture_source, Hint: the components the material wants (GL_SRGB_ALPHA ...)
texture_format texture_format_of(GLint Format, GLint Hint);
// a texture of the vulkan renderer
struct vulkan14_texture : public texture_source
{
explicit vulkan14_texture(resources const &Resources) : m_resources(Resources) {}
~vulkan14_texture() override;
bool create(bool const Static = false) override;
void release() override;
size_t get_id() const override
{
return static_cast<size_t>(m_id);
}
void update_from_memory(size_t width, size_t height, const uint8_t *data) override;
bool resident() const override
{
return m_made;
}
void destroy() override;
// the content given by update_from_memory() from another thread, uploaded by the thread of the renderer
void apply_pending();
protected:
void on_failed() override;
private:
void upload_rgba(std::uint8_t const *Data, std::size_t Width, std::size_t Height);
resources const &m_resources;
std::shared_ptr<image> m_image;
std::uint64_t m_id{0};
bool m_made{false};
// content from another thread, waiting for the thread of the renderer
std::mutex m_pendingmutex;
std::vector<std::uint8_t> m_pending;
std::size_t m_pendingwidth{0};
std::size_t m_pendingheight{0};
};
} // namespace vk14

View File

@@ -0,0 +1,19 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the implementation of the vulkan memory allocator (ref/VulkanMemoryAllocator). the functions of vulkan are given to it by
// vk14::allocator, as volk loaded them (vmaImportVulkanFunctionsFromVolk)
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14common.h"
#define VMA_STATIC_VULKAN_FUNCTIONS 0
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0
#define VMA_IMPLEMENTATION
#include <vk_mem_alloc.h>

View File

@@ -1,4 +1,6 @@
#include "rendering/renderer.h"
#include "gl/shader.h"
#include "gl/ubo.h"
#include "scripting/PyInt.h"
#include <GLFW/glfw3.h>

43
shaders/README.md Normal file
View File

@@ -0,0 +1,43 @@
# Shaders
| Directory | What is in it |
|---|---|
| `ogl46/` | GLSL sources of the OpenGL 4.6 renderer |
| `ogl46bin/` | SPIR-V binaries of `ogl46/` (optional, made by `compile_spirv.py`) |
| `vk14/` | GLSL sources of the Vulkan 1.4 renderer |
| `vk14bin/` | SPIR-V binaries of `vk14/` (optional, made by `compile_spirv.py`) |
The renderers load the binary of a shader first, when there is one and it was compiled from the sources that are there now
(`manifest.txt` of the directory of the binaries lists the sources of each binary with their hashes). Otherwise the source is
compiled: by the OpenGL driver for `ogl46/`, by glslang inside the simulator for `vk14/` (with a cache of the results in the
directory of the user). `gfx.shaders.binaries no` in `eu07.ini` makes them always use the sources. The sources are needed
even with the binaries: the directives of the materials (`#pragma texture`, `#pragma param`) are read from them.
## Compiling to SPIR-V
python shaders/compile_spirv.py
compiles every shader of `ogl46/` (with the variant `MODEL_DRAWS` of the vertex and fragment shaders) and of `vk14/` with
`glslc` (Vulkan SDK, `VULKAN_SDK`, or `--glslc <path>`). A single file compiles the same way by hand:
glslc --target-env=opengl4.5 ogl46/mat_default.frag -o ogl46bin/mat_default.frag.spv
glslc --target-env=opengl4.5 -DMODEL_DRAWS=1 ogl46/vertex.vert -o ogl46bin/vertex.vert.MODEL_DRAWS.spv
glslc --target-env=vulkan1.4 vk14/imgui.frag -o vk14bin/imgui.frag.spv
A binary made by hand isn't in the manifest, so it is taken without being compared with its source. After a change of a
source, compile the binaries again (or delete them): a binary older than its source is not used, and the log says so.
## Writing shaders
* Every stage file starts with `#version 460 core` and `#define STAGE_VERTEX 1` (`STAGE_FRAGMENT`, `STAGE_GEOMETRY`,
`STAGE_COMPUTE`); files included by others (`.glsl`) don't.
* Includes: `#include "file.glsl"`, relative to the directory of the file.
* Every input and output between the stages has an explicit `layout(location = N)`, every sampler, image and block an explicit
binding, every loose uniform an explicit location: SPIR-V has no names to link by. The locations of the inputs and outputs
of `ogl46/` are one table for all the shaders (a name has the same location everywhere), see `ogl46/common.glsl`.
* The settings of the game (`SHADOWMAP_ENABLED`, `ENVMAP_ENABLED`, `MOTIONBLUR_ENABLED`, `POSTFX_ENABLED`,
`EXTRAEFFECTS_ENABLED`, `OUTPUT_GAMMA`) are `const bool` (specialization constants in the binaries, `ogl46/settings.glsl`):
use them in `if`, not in `#if`.
* The constants of the game (bindings, sizes) are written in `ogl46/common.glsl` and `vk14/common.glsl` for the binaries; the
game puts its own values before the sources and checks at the start that the files have the same ones (the shader
validator in `ci_shadervalidator` checks them too).

View File

@@ -1,10 +0,0 @@
in vec2 f_coord;
#texture (tex1, 0, sRGB_A)
uniform sampler2D tex1;
void main()
{
if (texture(tex1, f_coord).a < 0.5f)
discard;
}

View File

@@ -1,25 +0,0 @@
in vec2 f_coord;
#texture (tex1, 0, sRGB_A)
uniform sampler2D tex1;
#include <common>
#include <tonemapping.glsl>
layout(location = 0) out vec4 out_color;
#if MOTIONBLUR_ENABLED
layout(location = 1) out vec4 out_motion;
#endif
void main()
{
vec4 tex_color = texture(tex1, f_coord);
#if POSTFX_ENABLED
out_color = tex_color * param[0];
#else
out_color = tonemap(tex_color * param[0]);
#endif
#if MOTIONBLUR_ENABLED
out_motion = vec4(0.0f);
#endif
}

View File

@@ -1,26 +0,0 @@
in vec3 f_color;
in vec4 f_pos;
#include <common>
#include <apply_fog.glsl>
#include <tonemapping.glsl>
layout(location = 0) out vec4 out_color;
#if MOTIONBLUR_ENABLED
layout(location = 1) out vec4 out_motion;
#endif
#include <sky_shoulder.glsl>
void main()
{
vec3 col = sky_shoulder(pow(max(f_color.rgb, vec3(0.0)), vec3(2.2)));
#if POSTFX_ENABLED
out_color = vec4(apply_fog(col), 1.0f);
#else
out_color = tonemap(vec4(apply_fog(col), 1.0f));
#endif
#if MOTIONBLUR_ENABLED
out_motion = vec4(0.0f);
#endif
}

130
shaders/compile_spirv.py Normal file
View File

@@ -0,0 +1,130 @@
#!/usr/bin/env python3
# compiles the shaders of shaders/ogl46 and shaders/vk14 to SPIR-V with glslc, into shaders/ogl46bin and shaders/vk14bin,
# which the renderers load before they compile the sources (gfx.shaders.binaries). each directory of binaries gets
# manifest.txt: the source files each binary was compiled from, with their hashes, so the game doesn't take a binary older
# than its sources.
#
# python shaders/compile_spirv.py [--glslc <path>] [--only ogl46|vk14] [<shaders directory>]
#
# glslc comes with the Vulkan SDK (VULKAN_SDK is looked at) or with the shaderc package. the shaders of opengl are compiled
# for --target-env=opengl4.5 (SPIR-V 1.0, which opengl 4.6 takes), with the variant of the multi draws (MODEL_DRAWS) of the
# vertex and fragment shaders; those of vulkan for --target-env=vulkan1.4. a single file can be compiled by hand the same way:
# glslc --target-env=opengl4.5 shaders/ogl46/mat_default.frag -o shaders/ogl46bin/mat_default.frag.spv
# glslc --target-env=opengl4.5 -DMODEL_DRAWS=1 shaders/ogl46/vertex.vert -o shaders/ogl46bin/vertex.vert.MODEL_DRAWS.spv
# glslc --target-env=vulkan1.4 shaders/vk14/imgui.frag -o shaders/vk14bin/imgui.frag.spv
# (a binary compiled by hand isn't in the manifest, so the game takes it without comparing it with its sources)
import argparse
import os
import re
import shutil
import subprocess
import sys
STAGES = ('.vert', '.frag', '.geom', '.comp')
TREES = {
# source directory, binary directory, target environment, variants of the vertex and fragment shaders
'ogl46': ('ogl46', 'ogl46bin', 'opengl4.5', ['MODEL_DRAWS']),
'vk14': ('vk14', 'vk14bin', 'vulkan1.4', []),
}
INCLUDE = re.compile(r'^[ \t]*#include[ \t]*[<"]([^>"]+)[>"]', re.M)
def file_hash(path):
# fnv-1a 64 of the text with the line ends made \n, the same as gfx::shader_file_hash()
with open(path, 'rb') as f:
data = f.read().replace(b'\r\n', b'\n')
h = 14695981039346656037
for b in data:
h ^= b
h = (h * 1099511628211) & 0xFFFFFFFFFFFFFFFF
return '%016x' % h
def dependencies(directory, name, found=None):
# the file and the files it includes, the same way as gfx::preprocess_shader()
if found is None:
found = []
if name in found:
return found
found.append(name)
with open(os.path.join(directory, name), 'r', encoding='utf-8', errors='replace') as f:
text = f.read()
for include in INCLUDE.findall(text):
if include == 'common':
include = 'common.glsl'
dependencies(directory, include, found)
return found
def find_glslc(explicit):
if explicit:
return explicit
sdk = os.environ.get('VULKAN_SDK')
if sdk:
for sub in ('Bin', 'bin'):
for exe in ('glslc.exe', 'glslc'):
path = os.path.join(sdk, sub, exe)
if os.path.isfile(path):
return path
return shutil.which('glslc')
def main():
parser = argparse.ArgumentParser(description='compiles the shaders of MaSzyna to SPIR-V with glslc')
parser.add_argument('shaders', nargs='?', default=os.path.dirname(os.path.abspath(__file__)), help='the shaders directory (the one of this script by default)')
parser.add_argument('--glslc', help='path of glslc')
parser.add_argument('--only', choices=sorted(TREES), help='only the shaders of one renderer')
args = parser.parse_args()
glslc = find_glslc(args.glslc)
if not glslc:
print('glslc not found: install the Vulkan SDK (VULKAN_SDK) or shaderc, or give --glslc', file=sys.stderr)
return 1
failed = []
count = 0
for key, (sourcedir, binarydir, env, variants) in TREES.items():
if args.only and args.only != key:
continue
sources = os.path.join(args.shaders, sourcedir)
binaries = os.path.join(args.shaders, binarydir)
if not os.path.isdir(sources):
continue
os.makedirs(binaries, exist_ok=True)
manifest = ['# made by compile_spirv.py: binary, source file it was compiled from, fnv-1a 64 of the source (line ends as \\n)']
names = sorted(n for n in os.listdir(sources) if os.path.splitext(n)[1] in STAGES)
for name in names:
jobs = [('', [])]
if os.path.splitext(name)[1] in ('.vert', '.frag'):
jobs += [(v, ['-D%s=1' % v]) for v in variants]
try:
deps = dependencies(sources, name)
except OSError as e:
failed.append('%s/%s (%s)' % (sourcedir, name, e))
continue
for variant, defines in jobs:
binary = name + ('.' + variant if variant else '') + '.spv'
command = [glslc, '--target-env=' + env] + defines + [os.path.join(sources, name), '-o', os.path.join(binaries, binary)]
result = subprocess.run(command, capture_output=True, text=True)
if result.returncode != 0:
failed.append('%s/%s' % (binarydir, binary))
sys.stderr.write(result.stderr)
continue
if result.stderr.strip():
sys.stderr.write(result.stderr)
count += 1
for dep in deps:
manifest.append('%s %s %s' % (binary, dep, file_hash(os.path.join(sources, dep))))
with open(os.path.join(binaries, 'manifest.txt'), 'w', newline='\n') as f:
f.write('\n'.join(manifest) + '\n')
print('%d binaries compiled' % count)
if failed:
print('failed: ' + ' '.join(failed), file=sys.stderr)
return 1
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@@ -1,11 +0,0 @@
in vec2 f_coords;
#texture (tex1, 0, sRGB)
uniform sampler2D tex1;
layout(location = 0) out vec4 out_color;
void main()
{
out_color = texture(tex1, f_coords);
}

View File

@@ -1,65 +0,0 @@
#if ENVMAP_ENABLED
uniform samplerCube envmap;
// the same map prefiltered with the GGX lobe (envmap_prefilter.comp), used when env_ggx is 1
uniform samplerCube envmap_ggx;
#endif
vec3 envmap_color( vec3 normal )
{
#if ENVMAP_ENABLED
vec3 refvec = reflect(f_pos.xyz, normal); // view space
refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space
vec3 envcolor = texture(envmap, refvec).rgb;
// Sanitize. The env cubemap can briefly contain NaN/Inf after a
// reflection-pass regeneration (m_empty_cubemap was bound during the
// face render and on NVIDIA can sample as undefined). Without this
// guard, a single bad texel propagates through env_reflection in
// apply_lights() and produces one-frame black flashes on glossy
// (esp. specgloss) surfaces.
if (any(isnan(envcolor)) || any(isinf(envcolor))) envcolor = vec3(0.0);
envcolor = max(envcolor, vec3(0.0));
#else
vec3 envcolor = vec3(0.5);
#endif
return envcolor;
}
// Roughness-aware env map lookup — uses mip levels for blurry reflections.
// lod 0.0 = mirror sharp, lod ~8.0 = fully diffuse blur.
vec3 envmap_color_lod(vec3 fragnormal, float lod)
{
#if ENVMAP_ENABLED
vec3 refvec = reflect(f_pos.xyz, fragnormal); // view space — matches envmap_color exactly
refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space — was missing
vec3 envcolor = textureLod(envmap, refvec, lod).rgb;
// See envmap_color() above — same NaN/Inf sanitize, also needed here
// because mipmap generation propagates a single NaN texel across the
// whole mip chain.
if (any(isnan(envcolor)) || any(isinf(envcolor))) envcolor = vec3(0.0);
return max(envcolor, vec3(0.0));
#else
return vec3(0.5); // was vec3(0.0), match the non-LOD fallback
#endif
}
// reflection of the surroundings in a surface of the given perceptual roughness (0 mirror, 1 matte)
vec3 envmap_color_rough(vec3 fragnormal, float roughness)
{
// the plain map, its mipmaps (box filtered) standing for the roughness
const float MAX_REFLECTION_LOD = 8.0;
vec3 boxcolor = envmap_color_lod(fragnormal, roughness * MAX_REFLECTION_LOD);
#if ENVMAP_ENABLED
if (env_ggx == 0U || roughness <= ENVMAP_GGX_ROUGHNESS_MIN)
return boxcolor;
vec3 refvec = reflect(f_pos.xyz, fragnormal);
refvec = vec3(inv_view * vec4(refvec, 0.0));
float level = (roughness - ENVMAP_GGX_ROUGHNESS_MIN) / (1.0 - ENVMAP_GGX_ROUGHNESS_MIN) * float(ENVMAP_GGX_LEVELS - 1U);
vec3 ggxcolor = textureLod(envmap_ggx, refvec, level).rgb;
if (any(isnan(ggxcolor)) || any(isinf(ggxcolor))) ggxcolor = vec3(0.0);
ggxcolor = max(ggxcolor, vec3(0.0));
// the prefiltered map takes over over the first tenth of the roughness above its lowest level
return mix(boxcolor, ggxcolor, clamp((roughness - ENVMAP_GGX_ROUGHNESS_MIN) * 10.0, 0.0, 1.0));
#else
return boxcolor;
#endif
}

View File

@@ -1,14 +0,0 @@
in vec2 f_coords;
layout(location = 0) out vec4 out_color;
#texture (tex1, 0, sRGB_A)
uniform sampler2D tex1;
void main()
{
vec2 texcoord = f_coords;
vec4 color = texture(tex1, texcoord);
out_color = color;
}

View File

@@ -1 +0,0 @@
#include <mat_colored.frag>

View File

@@ -1 +0,0 @@
#include <mat_default.frag>

View File

@@ -1 +0,0 @@
#include <mat_reflmap.frag>

View File

@@ -1,142 +0,0 @@
in vec3 f_normal;
in vec2 f_coord;
in vec4 f_pos;
in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos;
in vec4 f_clip_pos;
in vec4 f_clip_future_pos;
in vec3 TangentFragPos;
#include <common>
layout(location = 0) out vec4 out_color;
#if MOTIONBLUR_ENABLED
layout(location = 1) out vec4 out_motion;
#endif
#param (color, 0, 0, 4, diffuse)
#param (diffuse, 1, 0, 1, diffuse)
#param (specular, 1, 1, 1, specular)
#param (reflection, 1, 2, 1, one)
#param (glossiness, 1, 3, 1, glossiness)
#param (detail_scale, 2, 0, 1, one)
#param (detail_height_scale, 2, 1, 1, one)
#param (height_scale, 2, 2, 1, zero)
#param (height_offset, 2, 3, 1, zero)
#texture (diffuse, 0, sRGB_A)
uniform sampler2D diffuse;
#texture (normalmap, 1, RGBA)
uniform sampler2D normalmap;
#texture (detailnormalmap, 2, RGBA)
uniform sampler2D detailnormalmap;
#define PARALLAX
// param[2].w is the height offset, not the weathering
#define NO_WEATHERING
#include <light_common.glsl>
#include <apply_fog.glsl>
#include <tonemapping.glsl>
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir);
void main()
{
//parallax mapping
vec3 viewDir = normalize(-TangentFragPos); //tangent view pos - tangent frag pos
vec2 f_coord_p = ParallaxMapping(f_coord, viewDir);
vec4 normal_map = texture(normalmap, f_coord_p);
vec4 detailnormal_map = texture(detailnormalmap, f_coord_p * param[2].x);
vec4 tex_color = texture(diffuse, f_coord_p);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 normal;
vec3 normaldetail;
normaldetail.xy = detailnormal_map.rg* 2.0 - 1.0;
normaldetail.z = sqrt(1.0 - clamp((dot(normaldetail.xy, normaldetail.xy)), 0.0, 1.0));
normaldetail.xyz = normaldetail.xyz * param[2].y;
normal.xy = normal_map.rg* 2.0 - 1.0;
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(vec3(normal.xy + normaldetail.xy, normal.z)));
float reflectivity = param[1].z * normal_map.a;
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
legacy_gloss();
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
#if POSTFX_ENABLED
out_color = color;
#else
out_color = tonemap(color);
#endif
#if MOTIONBLUR_ENABLED
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
#endif
}
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir)
{
float pos_len = length(f_pos.xyz);
// gradients of the coordinates taken here: inside the loop the fragments of a quad don't go the same way
vec2 dcoorddx = dFdx(f_coord);
vec2 dcoorddy = dFdy(f_coord);
if (pos_len > 100.0) {
return f_coord;
}
// the shift fades out before the distance it stops at, so it doesn't switch off at once there
float fade = 1.0 - smoothstep(80.0, 100.0, pos_len);
#if EXTRAEFFECTS_ENABLED
const float minLayers = 8.0;
const float maxLayers = 32.0;
float LayersWeight = pos_len / 20.0;
vec2 currentTexCoords = f_coord;
float currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b;
LayersWeight = min(abs(dot(vec3(0.0, 0.0, 1.0), viewDir)),LayersWeight);
float numLayers = mix(maxLayers, minLayers, clamp(LayersWeight, 0.0, 1.0)); // number of depth layers
float layerDepth = 1.0 / numLayers; // calculate the size of each layer
float currentLayerDepth = 0.0; // depth of current layer
vec2 P = viewDir.xy * param[2].z * fade; // the amount to shift the texture coordinates per layer (from vector P)
vec2 deltaTexCoords = P / numLayers;
while(currentLayerDepth < currentDepthMapValue)
{
currentTexCoords -= deltaTexCoords; // shift texture coordinates along direction of P
currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b; // get depthmap value at current texture coordinates
currentLayerDepth += layerDepth; // get depth of next layer
}
vec2 prevTexCoords = currentTexCoords + deltaTexCoords; // get texture coordinates before collision (reverse operations)
float afterDepth = currentDepthMapValue - currentLayerDepth; // get depth after and before collision for linear interpolation
float beforeDepth = textureGrad(normalmap, prevTexCoords, dcoorddx, dcoorddy).b - currentLayerDepth + layerDepth;
float weight = afterDepth / (afterDepth - beforeDepth); // interpolation of texture coordinates
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
return finalTexCoords;
#else
float height = texture(normalmap, f_coord).b;
vec2 p = viewDir.xy / viewDir.z * (height * (param[2].z - param[2].w) * 0.2) * fade;
return f_coord - p;
#endif
}

View File

@@ -1,148 +0,0 @@
in vec3 f_normal;
in vec2 f_coord;
in vec4 f_pos;
in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos;
in vec4 f_clip_pos;
in vec4 f_clip_future_pos;
in vec3 TangentFragPos;
#include <common>
layout(location = 0) out vec4 out_color;
#if MOTIONBLUR_ENABLED
layout(location = 1) out vec4 out_motion;
#endif
#param (color, 0, 0, 4, diffuse)
#param (diffuse, 1, 0, 1, diffuse)
#param (specular, 1, 1, 1, specular)
#param (reflection, 1, 2, 1, one)
#param (glossiness, 1, 3, 1, glossiness)
#param (detail_scale, 2, 0, 1, one)
#param (detail_height_scale, 2, 1, 1, one)
#param (height_scale, 2, 2, 1, zero)
#param (height_offset, 2, 3, 1, zero)
#texture (diffuse, 0, sRGB_A)
uniform sampler2D diffuse;
#texture (normalmap, 1, RGBA)
uniform sampler2D normalmap;
#texture (specgloss, 2, RGBA)
uniform sampler2D specgloss;
#texture (detailnormalmap, 3, RGBA)
uniform sampler2D detailnormalmap;
#define PARALLAX
// param[2].w is the height offset, not the weathering
#define NO_WEATHERING
#include <light_common.glsl>
#include <apply_fog.glsl>
#include <tonemapping.glsl>
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir);
void main()
{
//parallax mapping
vec3 viewDir = normalize(vec3(0.0f, 0.0f, 0.0f) - TangentFragPos); //tangent view pos - tangent frag pos
vec2 f_coord_p = ParallaxMapping(f_coord, viewDir);
vec4 normal_map = texture(normalmap, f_coord_p);
vec4 detailnormal_map = texture(detailnormalmap, f_coord_p * param[2].x);
vec4 tex_color = texture(diffuse, f_coord_p);
vec4 specgloss_map = texture(specgloss, f_coord_p);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
vec3 fragcolor = ambient;
vec3 normal;
vec3 normaldetail;
normaldetail.xy = detailnormal_map.rg* 2.0 - 1.0;
normaldetail.z = sqrt(1.0 - clamp((dot(normaldetail.xy, normaldetail.xy)), 0.0, 1.0));
normaldetail.xyz = normaldetail.xyz * param[2].y;
normal.xy = normal_map.rg* 2.0 - 1.0;
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(vec3(normal.xy + normaldetail.xy, normal.z)));
float reflectivity = param[1].z * normal_map.a;
float specularity = specgloss_map.r;
glossiness = specgloss_map.g * abs(param[1].w);
metalic = specgloss_map.b;
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
#if POSTFX_ENABLED
out_color = color;
#else
out_color = tonemap(color);
#endif
#if MOTIONBLUR_ENABLED
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
#endif
}
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir)
{
float pos_len = length(f_pos.xyz);
// gradients of the coordinates taken here: inside the loop the fragments of a quad don't go the same way
vec2 dcoorddx = dFdx(f_coord);
vec2 dcoorddy = dFdy(f_coord);
if (pos_len > 100.0) {
return f_coord;
}
// the shift fades out before the distance it stops at, so it doesn't switch off at once there
float fade = 1.0 - smoothstep(80.0, 100.0, pos_len);
#if EXTRAEFFECTS_ENABLED
const float minLayers = 8.0;
const float maxLayers = 32.0;
float LayersWeight = pos_len / 20.0;
vec2 currentTexCoords = f_coord;
float currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b;
LayersWeight = min(abs(dot(vec3(0.0, 0.0, 1.0), viewDir)),LayersWeight);
float numLayers = mix(maxLayers, minLayers, clamp(LayersWeight, 0.0, 1.0)); // number of depth layers
float layerDepth = 1.0 / numLayers; // calculate the size of each layer
float currentLayerDepth = 0.0; // depth of current layer
vec2 P = viewDir.xy * param[2].z * fade; // the amount to shift the texture coordinates per layer (from vector P)
vec2 deltaTexCoords = P / numLayers;
while(currentLayerDepth < currentDepthMapValue)
{
currentTexCoords -= deltaTexCoords; // shift texture coordinates along direction of P
currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b; // get depthmap value at current texture coordinates
currentLayerDepth += layerDepth; // get depth of next layer
}
vec2 prevTexCoords = currentTexCoords + deltaTexCoords; // get texture coordinates before collision (reverse operations)
float afterDepth = currentDepthMapValue - currentLayerDepth; // get depth after and before collision for linear interpolation
float beforeDepth = textureGrad(normalmap, prevTexCoords, dcoorddx, dcoorddy).b - currentLayerDepth + layerDepth;
float weight = afterDepth / (afterDepth - beforeDepth); // interpolation of texture coordinates
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
return finalTexCoords;
#else
float height = texture(normalmap, f_coord).b;
vec2 p = viewDir.xy / viewDir.z * (height * (param[2].z - param[2].w) * 0.2) * fade;
return f_coord - p;
#endif
}

View File

@@ -1,20 +0,0 @@
#include <common>
#include <tonemapping.glsl>
layout(location = 0) out vec4 out_color;
#if MOTIONBLUR_ENABLED
layout(location = 1) out vec4 out_motion;
#endif
void main()
{
vec4 color = vec4(1.0, 0.0, 1.0, 1.0);
#if POSTFX_ENABLED
out_color = color;
#else
out_color = tonemap(color);
#endif
#if MOTIONBLUR_ENABLED
out_motion = vec4(0.0f);
#endif
}

View File

@@ -1,127 +0,0 @@
in vec3 f_normal;
in vec2 f_coord;
in vec4 f_pos;
in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos;
in vec4 f_clip_pos;
in vec4 f_clip_future_pos;
in vec3 TangentFragPos;
#include <common>
layout(location = 0) out vec4 out_color;
#if MOTIONBLUR_ENABLED
layout(location = 1) out vec4 out_motion;
#endif
#param (color, 0, 0, 4, diffuse)
#param (diffuse, 1, 0, 1, diffuse)
#param (specular, 1, 1, 1, specular)
#param (reflection, 1, 2, 1, one)
#param (glossiness, 1, 3, 1, glossiness)
#param (height_scale, 2, 1, 1, zero)
#param (height_offset, 2, 2, 1, zero)
#texture (diffuse, 0, sRGB_A)
uniform sampler2D diffuse;
#texture (normalmap, 1, RGBA)
uniform sampler2D normalmap;
#define PARALLAX
#include <light_common.glsl>
#include <apply_fog.glsl>
#include <tonemapping.glsl>
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir);
void main()
{
//parallax mapping
vec3 viewDir = normalize(vec3(0.0f, 0.0f, 0.0f) - TangentFragPos); //tangent view pos - tangent frag pos
vec2 f_coord_p = ParallaxMapping(f_coord, viewDir);
vec4 tex_color = texture(diffuse, f_coord_p);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 normal;
normal.xy = (texture(normalmap, f_coord_p).rg * 2.0 - 1.0);
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz));
float reflectivity = param[1].z * texture(normalmap, f_coord_p).a;
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
legacy_gloss();
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
#if POSTFX_ENABLED
out_color = color;
#else
out_color = tonemap(color);
#endif
#if MOTIONBLUR_ENABLED
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
#endif
}
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir)
{
float pos_len = length(f_pos.xyz);
// gradients of the coordinates taken here: inside the loop the fragments of a quad don't go the same way
vec2 dcoorddx = dFdx(f_coord);
vec2 dcoorddy = dFdy(f_coord);
if (pos_len > 100.0) {
return f_coord;
}
// the shift fades out before the distance it stops at, so it doesn't switch off at once there
float fade = 1.0 - smoothstep(80.0, 100.0, pos_len);
#if EXTRAEFFECTS_ENABLED
const float minLayers = 8.0;
const float maxLayers = 32.0;
float LayersWeight = pos_len / 20.0;
vec2 currentTexCoords = f_coord;
float currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b;
LayersWeight = min(abs(dot(vec3(0.0, 0.0, 1.0), viewDir)),LayersWeight);
float numLayers = mix(maxLayers, minLayers, clamp(LayersWeight, 0.0, 1.0)); // number of depth layers
float layerDepth = 1.0 / numLayers; // calculate the size of each layer
float currentLayerDepth = 0.0; // depth of current layer
vec2 P = viewDir.xy * param[2].y * fade; // the amount to shift the texture coordinates per layer (from vector P)
vec2 deltaTexCoords = P / numLayers;
while(currentLayerDepth < currentDepthMapValue)
{
currentTexCoords -= deltaTexCoords; // shift texture coordinates along direction of P
currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b; // get depthmap value at current texture coordinates
currentLayerDepth += layerDepth; // get depth of next layer
}
vec2 prevTexCoords = currentTexCoords + deltaTexCoords; // get texture coordinates before collision (reverse operations)
float afterDepth = currentDepthMapValue - currentLayerDepth; // get depth after and before collision for linear interpolation
float beforeDepth = textureGrad(normalmap, prevTexCoords, dcoorddx, dcoorddy).b - currentLayerDepth + layerDepth;
float weight = afterDepth / (afterDepth - beforeDepth); // interpolation of texture coordinates
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
return finalTexCoords;
#else
float height = texture(normalmap, f_coord).b;
vec2 p = viewDir.xy / viewDir.z * (height * (param[2].y - param[2].z) * 0.2) * fade;
return f_coord - p;
#endif
}

View File

@@ -1,132 +0,0 @@
in vec3 f_normal;
in vec2 f_coord;
in vec4 f_pos;
in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos;
in vec4 f_clip_pos;
in vec4 f_clip_future_pos;
in vec3 TangentFragPos;
#include <common>
layout(location = 0) out vec4 out_color;
#if MOTIONBLUR_ENABLED
layout(location = 1) out vec4 out_motion;
#endif
#param (color, 0, 0, 4, diffuse)
#param (diffuse, 1, 0, 1, diffuse)
#param (specular, 1, 1, 1, specular)
#param (reflection, 1, 2, 1, one)
#param (glossiness, 1, 3, 1, glossiness)
#param (height_scale, 2, 1, 1, zero)
#param (height_offset, 2, 2, 1, zero)
#texture (diffuse, 0, sRGB_A)
uniform sampler2D diffuse;
#texture (normalmap, 1, RGBA)
uniform sampler2D normalmap;
#texture (specgloss, 2, RGBA)
uniform sampler2D specgloss;
#define PARALLAX
#include <light_common.glsl>
#include <apply_fog.glsl>
#include <tonemapping.glsl>
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir);
void main()
{
//parallax mapping
vec3 viewDir = normalize(vec3(0.0f, 0.0f, 0.0f) - TangentFragPos); //tangent view pos - tangent frag pos
vec2 f_coord_p = ParallaxMapping(f_coord, viewDir);
vec4 tex_color = texture(diffuse, f_coord_p);
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
if(alphatestfail)
discard;
// if (tex_color.a < opacity)
// discard;
vec3 fragcolor = ambient;
vec3 normal;
normal.xy = (texture(normalmap, f_coord_p).rg * 2.0 - 1.0);
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz));
float reflectivity = param[1].z * texture(normalmap, f_coord_p).a;
float specularity = texture(specgloss, f_coord_p).r;
glossiness = texture(specgloss, f_coord_p).g * abs(param[1].w);
metalic = texture(specgloss, f_coord_p).b;
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
#if POSTFX_ENABLED
out_color = color;
#else
out_color = tonemap(color);
#endif
#if MOTIONBLUR_ENABLED
{
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
out_motion = vec4(a - b, 0.0f, 0.0f);
}
#endif
}
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir)
{
float pos_len = length(f_pos.xyz);
// gradients of the coordinates taken here: inside the loop the fragments of a quad don't go the same way
vec2 dcoorddx = dFdx(f_coord);
vec2 dcoorddy = dFdy(f_coord);
if (pos_len > 100.0) {
return f_coord;
}
// the shift fades out before the distance it stops at, so it doesn't switch off at once there
float fade = 1.0 - smoothstep(80.0, 100.0, pos_len);
#if EXTRAEFFECTS_ENABLED
const float minLayers = 8.0;
const float maxLayers = 32.0;
float LayersWeight = pos_len / 20.0;
vec2 currentTexCoords = f_coord;
float currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b;
LayersWeight = min(abs(dot(vec3(0.0, 0.0, 1.0), viewDir)),LayersWeight);
float numLayers = mix(maxLayers, minLayers, clamp(LayersWeight, 0.0, 1.0)); // number of depth layers
float layerDepth = 1.0 / numLayers; // calculate the size of each layer
float currentLayerDepth = 0.0; // depth of current layer
vec2 P = viewDir.xy * param[2].y * fade; // the amount to shift the texture coordinates per layer (from vector P)
vec2 deltaTexCoords = P / numLayers;
while(currentLayerDepth < currentDepthMapValue)
{
currentTexCoords -= deltaTexCoords; // shift texture coordinates along direction of P
currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b; // get depthmap value at current texture coordinates
currentLayerDepth += layerDepth; // get depth of next layer
}
vec2 prevTexCoords = currentTexCoords + deltaTexCoords; // get texture coordinates before collision (reverse operations)
float afterDepth = currentDepthMapValue - currentLayerDepth; // get depth after and before collision for linear interpolation
float beforeDepth = textureGrad(normalmap, prevTexCoords, dcoorddx, dcoorddy).b - currentLayerDepth + layerDepth;
float weight = afterDepth / (afterDepth - beforeDepth); // interpolation of texture coordinates
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
return finalTexCoords;
#else
float height = texture(normalmap, f_coord).b;
vec2 p = viewDir.xy / viewDir.z * (height * (param[2].y - param[2].z) * 0.2) * fade;
return f_coord - p;
#endif
}

View File

@@ -1,10 +0,0 @@
// the rails and the ballast of a track faded into those of the tracks joined at its ends, see trackblend.glsl
#texture (diffuse, 0, sRGB_A)
uniform sampler2D diffuse;
#texture (diffuse1, 1, sRGB_A)
uniform sampler2D diffuse1;
#texture (diffuse2, 2, sRGB_A)
uniform sampler2D diffuse2;
#texture (diffuse3, 3, sRGB_A)
uniform sampler2D diffuse3;
#include <trackblend.glsl>

View File

@@ -1,19 +0,0 @@
// the rails and the ballast of a track of a normal mapped material faded into those of the tracks joined at its ends, see trackblend.glsl
#define TRACKBLEND_NORMALMAP 1
#texture (diffuse, 0, sRGB_A)
uniform sampler2D diffuse;
#texture (diffuse1, 1, sRGB_A)
uniform sampler2D diffuse1;
#texture (diffuse2, 2, sRGB_A)
uniform sampler2D diffuse2;
#texture (diffuse3, 3, sRGB_A)
uniform sampler2D diffuse3;
#texture (normalmap, 4, RGBA)
uniform sampler2D normalmap;
#texture (normalmap1, 5, RGBA)
uniform sampler2D normalmap1;
#texture (normalmap2, 6, RGBA)
uniform sampler2D normalmap2;
#texture (normalmap3, 7, RGBA)
uniform sampler2D normalmap3;
#include <trackblend.glsl>

View File

@@ -1,22 +0,0 @@
// the rails and the ballast of a track of a parallax material faded into those of the tracks joined at its ends, see trackblend.glsl
#define TRACKBLEND_NORMALMAP 1
#define TRACKBLEND_PARALLAX 1
#param (height_scale, 2, 1, 1, zero)
#param (height_offset, 2, 2, 1, zero)
#texture (diffuse, 0, sRGB_A)
uniform sampler2D diffuse;
#texture (diffuse1, 1, sRGB_A)
uniform sampler2D diffuse1;
#texture (diffuse2, 2, sRGB_A)
uniform sampler2D diffuse2;
#texture (diffuse3, 3, sRGB_A)
uniform sampler2D diffuse3;
#texture (normalmap, 4, RGBA)
uniform sampler2D normalmap;
#texture (normalmap1, 5, RGBA)
uniform sampler2D normalmap1;
#texture (normalmap2, 6, RGBA)
uniform sampler2D normalmap2;
#texture (normalmap3, 7, RGBA)
uniform sampler2D normalmap3;
#include <trackblend.glsl>

View File

@@ -1,18 +0,0 @@
// the rails and the ballast of a track of a material with normal and specgloss maps faded into those of the tracks joined at its ends, see trackblend.glsl
#define TRACKBLEND_NORMALMAP 1
#define TRACKBLEND_SPECGLOSS 1
#texture (diffuse, 0, sRGB_A)
uniform sampler2D diffuse;
#texture (diffuse1, 1, sRGB_A)
uniform sampler2D diffuse1;
#texture (diffuse2, 2, sRGB_A)
uniform sampler2D diffuse2;
#texture (normalmap, 3, RGBA)
uniform sampler2D normalmap;
#texture (normalmap1, 4, RGBA)
uniform sampler2D normalmap1;
#texture (normalmap2, 5, RGBA)
uniform sampler2D normalmap2;
#texture (specgloss, 6, RGBA)
uniform sampler2D specgloss;
#include <trackblend.glsl>

View File

@@ -0,0 +1,12 @@
#version 460 core
#define STAGE_FRAGMENT 1
layout(location = 2) in vec2 f_coord;
#pragma texture(tex1, 0, sRGB_A)
layout(binding = 0) uniform sampler2D tex1;
void main()
{
if (texture(tex1, f_coord).a < 0.5f)
discard;
}

View File

@@ -1,6 +1,6 @@
// fog between the camera and a fragment, see fog_volume.glsl and weather_common.glsl. a draw with a fog_density of 0 is not
// fogged; one with another density than the regular one (the glare of lamps) gets the optical depth scaled by the ratio
#include <fog_volume.glsl>
#include "fog_volume.glsl"
// rgb: light the fog adds, a: what it lets through
vec4 fog_at_fragment()

View File

@@ -1,10 +1,12 @@
#version 460 core
#define STAGE_COMPUTE 1
// what the cpu needs from the atmosphere, worked out from the sky-view table in one work group: the light arriving at the
// observer from every direction as spherical harmonics (the ground below the horizon lit by the sun and the sky), the
// transmittance towards the sun, and the average light of the sky above the horizon and over the whole sky.
// run after the sky-view table; the result is read back by the cpu a few frames later
#include <common>
#include <atmosphere_common.glsl>
#include "common.glsl"
#include "atmosphere_common.glsl"
layout(binding = ATMOSPHERE_TRANSMITTANCE_UNIT) uniform sampler2D atmo_transmittance_lut;
layout(binding = ATMOSPHERE_SKYVIEW_UNIT) uniform sampler2D atmo_skyview_lut;

View File

@@ -1,12 +1,14 @@
#version 460 core
#define STAGE_COMPUTE 1
// light scattered more than once, for the multiple scattering table (Hillaire 2020, section 5.5): for a point at a height
// lit by the sun at an angle, the second order scattering from 64 directions around it, summed as a geometric series of
// all the orders. one work group per texel, one invocation per direction. run after the transmittance table
#include <common>
#include <atmosphere_common.glsl>
#include "common.glsl"
#include "atmosphere_common.glsl"
layout(binding = ATMOSPHERE_TRANSMITTANCE_UNIT) uniform sampler2D atmo_transmittance_lut;
#include <atmosphere_integrate.glsl>
#include "atmosphere_integrate.glsl"
layout(local_size_x = 64) in;

View File

@@ -1,14 +1,16 @@
#version 460 core
#define STAGE_COMPUTE 1
// light of the sky seen by the observer in every direction, for the sky-view table (atmosphere_common.glsl), which the
// sky, the ambient light and the fog colour are read from. one invocation per texel. run every frame the sky is drawn with
// the atmosphere, after the transmittance and multiple scattering tables
#include <common>
#include <atmosphere_common.glsl>
#include "common.glsl"
#include "atmosphere_common.glsl"
layout(binding = ATMOSPHERE_TRANSMITTANCE_UNIT) uniform sampler2D atmo_transmittance_lut;
layout(binding = ATMOSPHERE_MULTISCATTERING_UNIT) uniform sampler2D atmo_multiscattering_lut;
#define ATMO_MULTISCATTERING_LUT
#include <atmosphere_integrate.glsl>
#include "atmosphere_integrate.glsl"
layout(local_size_x = 8, local_size_y = 8) in;

View File

@@ -1,8 +1,10 @@
#version 460 core
#define STAGE_COMPUTE 1
// transmittance from a point in the atmosphere to its top, for the transmittance table (atmosphere_common.glsl).
// one invocation per texel. run when the parameters of the atmosphere change
#include <common>
#include <atmosphere_common.glsl>
#include "common.glsl"
#include "atmosphere_common.glsl"
layout(local_size_x = 8, local_size_y = 8) in;

View File

@@ -0,0 +1,29 @@
#version 460 core
#define STAGE_FRAGMENT 1
layout(location = 2) in vec2 f_coord;
#pragma texture(tex1, 0, sRGB_A)
layout(binding = 0) uniform sampler2D tex1;
#include "common.glsl"
#include "tonemapping.glsl"
layout(location = 0) out vec4 out_color;
layout(location = 1) out vec4 out_motion;
void main()
{
vec4 tex_color = texture(tex1, f_coord);
if (POSTFX_ENABLED)
{
out_color = tex_color * param[0];
}
else
{
out_color = tonemap(tex_color * param[0]);
}
if (MOTIONBLUR_ENABLED)
{
out_motion = vec4(0.0f);
}
}

View File

@@ -1,17 +1,19 @@
#version 460 core
#define STAGE_FRAGMENT 1
// the sun, the moon and the textured sprite of the easter egg, drawn over the sky on a quad around the direction of the body
// (celestial.vert). param[0]: colour (rgb) and alpha, param[1]: position in view space and half the size of the quad,
// param[2].xy: corner (sprite) or centre (moon) of the picture in the texture, param[2].z: its size (sprite) or the radius of
// the disc in it (moon), in texture units, param[2].w: kind of body, 0: textured sprite, 1: sun, 2: moon.
// the colour goes out premultiplied, the alpha is how much of the sky behind the body it hides (blending ONE, ONE_MINUS_SRC_ALPHA)
in vec2 f_coord;
in vec2 f_local;
layout(location = 2) in vec2 f_coord;
layout(location = 15) in vec2 f_local;
#texture (tex1, 0, sRGB_A)
uniform sampler2D tex1;
#pragma texture(tex1, 0, sRGB_A)
layout(binding = 0) uniform sampler2D tex1;
#include <common>
#include <tonemapping.glsl>
#include "common.glsl"
#include "tonemapping.glsl"
// shape and light of the sun and the moon, set by opengl46_renderer::Render(world_environment *):
// [0].xy: direction to the zenith on the quad, [0].z: height over width of the disc (flattened by the refraction near the
@@ -22,9 +24,7 @@ uniform sampler2D tex1;
layout(location = 0) uniform vec4 body[3];
layout(location = 0) out vec4 out_color;
#if MOTIONBLUR_ENABLED
layout(location = 1) out vec4 out_motion;
#endif
void main()
{
@@ -72,12 +72,16 @@ void main()
color = vec4(albedo * param[0].rgb * ((lit + body[1].w) * coverage), coverage * param[0].a);
}
}
#if POSTFX_ENABLED
out_color = color;
#else
out_color = vec4(tonemap(vec4(color.rgb, 1.0)).rgb, color.a);
#endif
#if MOTIONBLUR_ENABLED
out_motion = vec4(0.0f);
#endif
if (POSTFX_ENABLED)
{
out_color = color;
}
else
{
out_color = vec4(tonemap(vec4(color.rgb, 1.0)).rgb, color.a);
}
if (MOTIONBLUR_ENABLED)
{
out_motion = vec4(0.0f);
}
}

View File

@@ -1,3 +1,5 @@
#version 460 core
#define STAGE_VERTEX 1
const vec2 vert[4] = vec2[]
(
vec2(-1.0, 1.0),
@@ -6,11 +8,11 @@ const vec2 vert[4] = vec2[]
vec2( 1.0, -1.0)
);
out vec2 f_coord;
layout(location = 2) out vec2 f_coord;
// the corner of the quad, -1...1 across it
out vec2 f_local;
layout(location = 15) out vec2 f_local;
#include <common>
#include "common.glsl"
void main()
{

View File

@@ -2,7 +2,7 @@
// the shape noise (3d, clouds_noise.comp) gives the lumps of a cloud, the detail noise (3d) eats into their edges, the coverage
// noise (2d) spreads the clouds of a layer over the sky so they take up its coverage. positions are world offsets from the
// camera, in metres, y up; the earth curves away from the camera, so far clouds sink below the horizon
#include <weather_common.glsl>
#include "weather_common.glsl"
layout(binding = CLOUD_SHAPE_TEX) uniform sampler3D cloud_shape_noise;
layout(binding = CLOUD_DETAIL_TEX) uniform sampler3D cloud_detail_noise;

View File

@@ -1,11 +1,13 @@
#version 460 core
#define STAGE_COMPUTE 1
// clouds of the weather ray marched through their layers (clouds_common.glsl), into an image the sky pass lays over the sky
// (sky_weather.frag). mode 0: the view of the main camera at a part of the resolution, smoothed over frames by
// clouds_temporal.comp; mode 1: a panorama around the camera (azimuth across, elevation up), a band of rows per frame, for the
// environment map and the other views. rgb: the light of the clouds with the fog in front of them, a: what they let through
// of the sky behind; the depth image gets the distance of the clouds, for the reprojection
#include <common>
#include <clouds_common.glsl>
#include "common.glsl"
#include "clouds_common.glsl"
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;

View File

@@ -1,10 +1,12 @@
#version 460 core
#define STAGE_COMPUTE 1
// the tiling noises the clouds and the fog are made of, made once at the start (opengl46_weather::init).
// kind 0, the shape (3d): r perlin-worley (perlin fbm with the worley cells carved in), g b a worley fbm of rising frequency;
// kind 1, the detail (3d): r g b worley fbm of rising frequency;
// kind 2, the coverage (2d): r g b three perlin fbm, spread evenly over 0-1 so a threshold of 1 - c leaves a share c above it
#include <common>
#include <random.glsl>
#include "common.glsl"
#include "random.glsl"
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;

View File

@@ -1,9 +1,11 @@
#version 460 core
#define STAGE_COMPUTE 1
// the shadow the clouds cast on the ground: for points of the ground reference around the camera, what the clouds let through
// towards the key light. the shading (light_common.glsl) and the fog (fog_inject.comp) look it up along the ray of the sun,
// see cloud_shadow() in weather_common.glsl
#include <common>
#include <clouds_common.glsl>
#include "common.glsl"
#include "clouds_common.glsl"
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;

View File

@@ -1,9 +1,11 @@
#version 460 core
#define STAGE_COMPUTE 1
// the clouds of the main view smoothed over frames: the image of this frame (clouds_march.comp, ray started at another
// distance every frame) blended into the one of the previous frames, found where the clouds were by the distance of the
// clouds, the rotation and the move of the camera. the history is held to the range of the neighbourhood of this frame,
// so moving clouds and a turning camera don't leave trails
#include <common>
#include "common.glsl"
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;

View File

@@ -1,7 +1,9 @@
#version 460 core
#define STAGE_COMPUTE 1
// bounding boxes of the light clusters in view space, see gl/cluster_grid.h
// one invocation per cluster, run when the projection or the depth range of the pass changes
#include <common>
#include "common.glsl"
layout(local_size_x = CLUSTER_BUILD_GROUP_SIZE) in;

View File

@@ -1,9 +1,11 @@
#version 460 core
#define STAGE_COMPUTE 1
// assigns the lights to the light clusters, see gl/cluster_grid.h
// one invocation per cluster: tests every light of light_ssbo (the sun, slot 0, is handled apart from the clusters)
// against the bounding box of the cluster in view space, and writes the indices of the lights which reach it.
// run every frame after the lights were updated, for the grid of the pass about to be drawn
#include <common>
#include "common.glsl"
layout(local_size_x = CLUSTER_CULL_GROUP_SIZE) in;

View File

@@ -1,13 +1,15 @@
#version 460 core
#define STAGE_FRAGMENT 1
// debug view of the light clusters: tints every pixel of the scene with the colour of the number of lights which
// were assigned to its cluster (see gl/cluster_grid.h), pixels of clusters without lights stay as they are.
// drawn over the resolved image
in vec2 f_coords;
layout(location = 14) in vec2 f_coords;
#include <common>
#include "common.glsl"
#texture (depth_tex, 0, R)
uniform sampler2D depth_tex;
#pragma texture(depth_tex, 0, R)
layout(binding = 0) uniform sampler2D depth_tex;
layout(location = 0) out vec4 out_color;

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