diff --git a/.github/workflows/shaders.yml b/.github/workflows/shaders.yml index f1152fe5..2d6135de 100644 --- a/.github/workflows/shaders.yml +++ b/.github/workflows/shaders.yml @@ -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 \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 589bb84b..273c15eb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 6dd55612..71fc460e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}) diff --git a/application/application.cpp b/application/application.cpp index 0ad93f69..1b6d00ca 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -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()) { diff --git a/application/application.h b/application/application.h index 1306c1c7..f3434cb2 100644 --- a/application/application.h +++ b/application/application.h @@ -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 m_crashui; modeptr_array m_modes { nullptr }; // collection of available application behaviour modes mode_stack m_modestack; // current behaviour mode diff --git a/application/scenarioloaderuilayer.cpp b/application/scenarioloaderuilayer.cpp index 0a55d1be..119da9c5 100644 --- a/application/scenarioloaderuilayer.cpp +++ b/application/scenarioloaderuilayer.cpp @@ -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 #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(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(image_data), image_width, image_height, options); delete[] image_data; diff --git a/application/scenarioloaderuilayer.h b/application/scenarioloaderuilayer.h index 72d7c3df..45a0ba5b 100644 --- a/application/scenarioloaderuilayer.h +++ b/application/scenarioloaderuilayer.h @@ -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 m_trivia; public: diff --git a/application/uilayer.cpp b/application/uilayer.cpp index a2b49b00..19bea078 100644 --- a/application/uilayer.cpp +++ b/application/uilayer.cpp @@ -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(); } diff --git a/ci_shadervalidator/build.sh b/ci_shadervalidator/build.sh index 450b2b98..f299f63c 100755 --- a/ci_shadervalidator/build.sh +++ b/ci_shadervalidator/build.sh @@ -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 diff --git a/ci_shadervalidator/main.cpp b/ci_shadervalidator/main.cpp index 196b275e..38a6553f 100644 --- a/ci_shadervalidator/main.cpp +++ b/ci_shadervalidator/main.cpp @@ -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 #include #include #include @@ -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 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 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 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 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); + } } } diff --git a/gl/glsl_common.cpp b/gl/glsl_common.cpp index 16fcb3f3..9e6e747b 100644 --- a/gl/glsl_common.cpp +++ b/gl/glsl_common.cpp @@ -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 gl::glsl_settings_values() +{ + // in the order of gl::shader_setting + return { + static_cast(Global.gfx_shadowmap_enabled), + static_cast(Global.gfx_envmap_enabled), + static_cast(Global.gfx_postfx_motionblur_enabled), + static_cast(!Global.gfx_skippipeline), + static_cast(Global.gfx_extraeffects), + static_cast(Global.gfx_shadergamma) }; +} + +bool gl::glsl_common_matches(std::string const &Basedir, std::vector &Differences) +{ + return gfx::shader_constants_match(gfx::shader_api::opengl46, "common.glsl", glsl_common, Basedir, Differences); } diff --git a/gl/glsl_common.h b/gl/glsl_common.h index 17e46723..e272b07b 100644 --- a/gl/glsl_common.h +++ b/gl/glsl_common.h @@ -1,10 +1,22 @@ #pragma once +#include +#include +#include + #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 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 &Differences); } diff --git a/gl/shader.cpp b/gl/shader.cpp index 6be32459..3fb0dcc8 100644 --- a/gl/shader.cpp +++ b/gl/shader.cpp @@ -1,292 +1,232 @@ #include "stdafx.h" -#include -#include #include "shader.h" #include "glsl_common.h" #include "utilities/Logs.h" -inline bool strcend(std::string_view value, std::string_view ending) +#include + +// the values of the components are those of opengl (material textures take them as the hint of their format) +static_assert(static_cast(gfx::shader_components::R) == GL_RED); +static_assert(static_cast(gfx::shader_components::RG) == GL_RG); +static_assert(static_cast(gfx::shader_components::RGB) == GL_RGB); +static_assert(static_cast(gfx::shader_components::RGBA) == GL_RGBA); +static_assert(static_cast(gfx::shader_components::sRGB) == GL_SRGB); +static_assert(static_cast(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 ) +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::max(length, 1)), '\0'); + glGetShaderInfoLog(Shader, static_cast(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::max(length, 1)), '\0'); + glGetProgramInfoLog(Program, static_cast(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 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(words.size() * sizeof(std::uint32_t))); + // the settings the module has, the others aren't given + auto const values{glsl_settings_values()}; + std::vector indices; + std::vector 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(indices.size()), indices.data(), settings.data()); -std::unordered_map 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 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 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 info{}; - glGetShaderInfoLog(*this, static_cast(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(e.id)); + GLint loc = glGetUniformLocation(*this, texturename.c_str()); + if (loc >= 0) + glUniform1i(loc, static_cast(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(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 info{}; - glGetProgramInfoLog(*this, static_cast(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 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(); } diff --git a/gl/shader.h b/gl/shader.h index a3b4d628..a8d272c6 100644 --- a/gl/shader.h +++ b/gl/shader.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -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 texture_conf; - std::unordered_map 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 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 components_mapping; - static std::unordered_map 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 + class program : public gfx::shader_program, public object, public bindable { 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 texture_conf; - std::unordered_map param_conf; + // whether the program was linked from SPIR-V binaries + bool is_binary() const + { + return m_binary; + } private: void init(); + + std::vector 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(); } diff --git a/gl/ubo.h b/gl/ubo.h index a070b78f..df42abca 100644 --- a/gl/ubo.h +++ b/gl/ubo.h @@ -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; diff --git a/launcher/scenery_wizard.cpp b/launcher/scenery_wizard.cpp index f53b88f1..c27affbb 100644 --- a/launcher/scenery_wizard.cpp +++ b/launcher/scenery_wizard.cpp @@ -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 #include #include @@ -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; } diff --git a/launcher/scenery_wizard.h b/launcher/scenery_wizard.h index cd22dff7..e3c282c5 100644 --- a/launcher/scenery_wizard.h +++ b/launcher/scenery_wizard.h @@ -54,7 +54,7 @@ class scenerywizard_panel : public ui_panel bool m_dragging{false}; std::future 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; diff --git a/model/Texture.cpp b/model/Texture.cpp index 71820861..ce063104 100644 --- a/model/Texture.cpp +++ b/model/Texture.cpp @@ -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 opengl_texture::units = { 0 }; GLint opengl_texture::m_activeunit = -1; -std::unordered_map opengl_texture::precompressed_formats = +std::unordered_map texture_source::precompressed_formats = { { GL_COMPRESSED_RGBA8_ETC2_EAC, 16 }, { GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, 16 }, @@ -257,16 +257,25 @@ std::unordered_map> opengl_texture::mapp { GL_RED, GL_R8 } } }, }; -texture_manager::texture_manager() { +texture_bank::texture_bank( std::function 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(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(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( 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( -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( 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( 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 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 -texture_manager::find_on_disk( std::string const &Texturename ) { +texture_bank::find_on_disk( std::string const &Texturename ) { std::vector const filenames { Global.asCurrentTexturePath + Texturename, diff --git a/model/Texture.h b/model/Texture.h index 8791a9f2..5fb5d70a 100644 --- a/model/Texture.h +++ b/model/Texture.h @@ -15,40 +15,33 @@ http://mozilla.org/MPL/2.0/. #include "winheaders.h" #include "utilities/memorystats.h" #include +#include #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::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 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 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 is_loaded{ false }; // indicates the texture data was loaded and can be processed std::atomic 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 precompressed_formats; - static std::unordered_map drivercompressed_formats; - static std::unordered_map> 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::max() }; // id value of texture without GL resource + GLuint id{ invalid_id }; // associated GL resource + + GLenum target = GL_TEXTURE_2D; + static std::array 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 drivercompressed_formats; + static std::unordered_map> 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 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 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 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( texture_bank::mark_as_used( Texture ) ); } + // provides direct access to specified texture object + opengl_texture & + texture( texture_handle const Texture ) const { + return static_cast( texture_bank::texture( Texture ) ); } + +protected: + void + released() override; +}; + // reduces provided data image to half of original size, using basic 2x2 average template void diff --git a/model/material.cpp b/model/material.cpp index 640f509c..c93f77b4 100644 --- a/model/material.cpp +++ b/model/material.cpp @@ -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::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( 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; diff --git a/model/material.h b/model/material.h index 802ac6ea..1baaae94 100644 --- a/model/material.h +++ b/model/material.h @@ -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_variants; std::array params; - std::vector params_state; + std::vector params_state; - std::shared_ptr shader; + std::shared_ptr shader; std::optional opacity; std::optional selfillum; float glossiness { 10.f }; diff --git a/ref/Vulkan-Headers b/ref/Vulkan-Headers new file mode 160000 index 00000000..6802bb47 --- /dev/null +++ b/ref/Vulkan-Headers @@ -0,0 +1 @@ +Subproject commit 6802bb4733b63ed5efd3adb308a6c885ef180ea1 diff --git a/ref/VulkanMemoryAllocator b/ref/VulkanMemoryAllocator new file mode 160000 index 00000000..1d8f600f --- /dev/null +++ b/ref/VulkanMemoryAllocator @@ -0,0 +1 @@ +Subproject commit 1d8f600fd424278486eade7ed3e877c99f0846b1 diff --git a/ref/glslang b/ref/glslang new file mode 160000 index 00000000..e1b562a8 --- /dev/null +++ b/ref/glslang @@ -0,0 +1 @@ +Subproject commit e1b562a8bed273a02f30b59b66a5d499793cede5 diff --git a/ref/volk b/ref/volk new file mode 160000 index 00000000..54fc0d7a --- /dev/null +++ b/ref/volk @@ -0,0 +1 @@ +Subproject commit 54fc0d7a72887cbfc5b030d26897b2b40ac53fb8 diff --git a/rendering/common/shadersource.cpp b/rendering/common/shadersource.cpp new file mode 100644 index 00000000..359f7590 --- /dev/null +++ b/rendering/common/shadersource.cpp @@ -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 +#include +#include +#include +#include +#include + +#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 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 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 (...), or the older # (...) +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 take_directive(std::string &Text, std::size_t const Start, std::size_t const Arguments) +{ + std::vector 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::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::stoul(args[1])); + conf.offset = static_cast(std::stoul(args[2])); + conf.size = static_cast(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::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(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>> entries; +}; + +std::unordered_map 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(linestream >> directive)) + { + continue; + } + if ((directive != "#define") || (false == static_cast(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(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 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 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 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 spirv_specialization_ids(std::vector const &Words) +{ + std::vector 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 + if ((opcode == 71) && (count >= 4) && (Words[index + 2] == 1)) + { + result.push_back(Words[index + 3]); + } + index += count; + } + return result; +} + +std::unordered_map shader_file_defines(shader_api const Api, std::string const &Name, std::string const &Basedir) +{ + std::unordered_map 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 &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 diff --git a/rendering/common/shadersource.h b/rendering/common/shadersource.h new file mode 100644 index 00000000..9387d984 --- /dev/null +++ b/rendering/common/shadersource.h @@ -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, .spv, and a variant of the +// shader (the defines of the renderer, e.g. MODEL_DRAWS) ..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 +#include +#include +#include +#include +#include +#include + +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; +using shader_param_conf = std::unordered_map; + +// 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 ) + std::string text; + // the file and its includes, relative to the directory of the sources, by their numbers + std::vector 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 ; +// 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 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 spirv_specialization_ids(std::vector 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 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 &Differences); + +} // namespace gfx diff --git a/rendering/nullrenderer.h b/rendering/nullrenderer.h index 0b21580b..bc29be7b 100644 --- a/rendering/nullrenderer.h +++ b/rendering/nullrenderer.h @@ -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 override { throw std::runtime_error("not impl"); } + auto Fetch_Shader( std::string const &name ) -> std::shared_ptr 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"); } diff --git a/rendering/opengl46/opengl46grass.cpp b/rendering/opengl46/opengl46grass.cpp index f208cd07..a72f2017 100644 --- a/rendering/opengl46/opengl46grass.cpp +++ b/rendering/opengl46/opengl46grass.cpp @@ -95,19 +95,19 @@ bool opengl46_grass::init() return false; } m_vao = std::make_unique(); - 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; } diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index 4e5df29e..1be23268 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -200,8 +200,8 @@ bool opengl46_renderer::Init(GLFWwindow *Window) return false; } - scene_ubo = std::make_unique(sizeof(gl::scene_ubs), 0); - model_ubo = std::make_unique(sizeof(gl::model_ubs), 1, Global.gfx_bufferstreaming); + scene_ubo = std::make_unique(sizeof(gl::scene_ubs), gl::SCENE_UBO_BINDING); + model_ubo = std::make_unique(sizeof(gl::model_ubs), gl::MODEL_UBO_BINDING, Global.gfx_bufferstreaming); light_ssbo = std::make_unique(sizeof(gl::light_data), gl::LIGHT_SSBO_BINDING); light_ssbo->bind_base(); { @@ -673,6 +673,8 @@ std::unique_ptr 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 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 const &Program) const +std::string opengl46_renderer::Shader_Name(std::shared_ptr 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 const &P return {}; } -std::shared_ptr opengl46_renderer::Fetch_Shader(const std::string &name) +std::shared_ptr 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(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( material.shader.get() ) ) }; if( lookup != m_prepassclasses.end() ) { return lookup->second; } // names the programs are fetched by: mat_.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( 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( 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(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(texture)); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast(unpack)); glPixelStorei(GL_UNPACK_ALIGNMENT, alignment); diff --git a/rendering/opengl46/opengl46renderer.h b/rendering/opengl46/opengl46renderer.h index 9141174c..41317772 100644 --- a/rendering/opengl46/opengl46renderer.h +++ b/rendering/opengl46/opengl46renderer.h @@ -117,8 +117,8 @@ class opengl46_renderer : public gfx_renderer { void Terrain_Grass( std::vector const &Kinds, std::vector const &Chunks ) override; // shader methods - auto Fetch_Shader( std::string const &name ) -> std::shared_ptr override; - auto Shader_Name( std::shared_ptr const &Program ) const -> std::string override; + auto Fetch_Shader( std::string const &name ) -> std::shared_ptr override; + auto Shader_Name( std::shared_ptr 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; diff --git a/rendering/opengl46/opengl46terrain.h b/rendering/opengl46/opengl46terrain.h index abcf74ab..48ee9a02 100644 --- a/rendering/opengl46/opengl46terrain.h +++ b/rendering/opengl46/opengl46terrain.h @@ -92,8 +92,8 @@ class opengl46_terrain_materials std::vector m_slots; std::vector m_free; std::unordered_map m_slotof; - std::shared_ptr m_program; // of the chunks, mat_terrain - std::shared_ptr m_holesprogram; // of the chunks with holes, mat_terrain_holes + std::shared_ptr m_program; // of the chunks, mat_terrain + std::shared_ptr m_holesprogram; // of the chunks with holes, mat_terrain_holes std::unique_ptr m_copy; std::unique_ptr m_framebuffer; }; diff --git a/rendering/renderer.h b/rendering/renderer.h index f33a545a..930f2cd5 100644 --- a/rendering/renderer.h +++ b/rendering/renderer.h @@ -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 const &Kinds, std::vector const &Chunks ) {} // shader methods - virtual auto Fetch_Shader( std::string const &name ) -> std::shared_ptr = 0; + virtual auto Fetch_Shader( std::string const &name ) -> std::shared_ptr = 0; // the name the program was fetched by, empty where it wasn't fetched by Fetch_Shader() - virtual auto Shader_Name( std::shared_ptr const &Program ) const -> std::string { return {}; } + virtual auto Shader_Name( std::shared_ptr 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) {} }; diff --git a/rendering/screenshot.cpp b/rendering/screenshot.cpp index de63c7fb..d081fb50 100644 --- a/rendering/screenshot.cpp +++ b/rendering/screenshot.cpp @@ -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(); } diff --git a/rendering/screenshot.h b/rendering/screenshot.h index 47226e29..97af757a 100644 --- a/rendering/screenshot.h +++ b/rendering/screenshot.h @@ -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); }; diff --git a/rendering/vulkan14/vulkan14bindings.h b/rendering/vulkan14/vulkan14bindings.h new file mode 100644 index 00000000..dff38364 --- /dev/null +++ b/rendering/vulkan14/vulkan14bindings.h @@ -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 +#include + +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 diff --git a/rendering/vulkan14/vulkan14capabilities.cpp b/rendering/vulkan14/vulkan14capabilities.cpp index a954897b..b9b31ea4 100644 --- a/rendering/vulkan14/vulkan14capabilities.cpp +++ b/rendering/vulkan14/vulkan14capabilities.cpp @@ -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()); diff --git a/rendering/vulkan14/vulkan14capabilities.h b/rendering/vulkan14/vulkan14capabilities.h index 9c1dfdef..bc688282 100644 --- a/rendering/vulkan14/vulkan14capabilities.h +++ b/rendering/vulkan14/vulkan14capabilities.h @@ -17,6 +17,8 @@ http://mozilla.org/MPL/2.0/. #include "rendering/vulkan14/vulkan14common.h" +#include + 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 pipeline_cache_uuid{}; + std::uint32_t max_push_descriptors{0}; // device extensions bool ext_swapchain{false}; diff --git a/rendering/vulkan14/vulkan14common.cpp b/rendering/vulkan14/vulkan14common.cpp index a8aa9ed8..936f9395 100644 --- a/rendering/vulkan14/vulkan14common.cpp +++ b/rendering/vulkan14/vulkan14common.cpp @@ -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); diff --git a/rendering/vulkan14/vulkan14common.h b/rendering/vulkan14/vulkan14common.h index 0301bc39..be3ca65b 100644 --- a/rendering/vulkan14/vulkan14common.h +++ b/rendering/vulkan14/vulkan14common.h @@ -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 std::uint64_t handle_value(Handle_ Handle) +{ + if constexpr (std::is_pointer_v) + { + return reinterpret_cast(Handle); + } + else + { + return static_cast(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); diff --git a/rendering/vulkan14/vulkan14descriptors.cpp b/rendering/vulkan14/vulkan14descriptors.cpp new file mode 100644 index 00000000..1a5a21ea --- /dev/null +++ b/rendering/vulkan14/vulkan14descriptors.cpp @@ -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 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(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 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(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 const &Sets, std::vector const &PushConstants, std::string const &Name) +{ + VkPipelineLayoutCreateInfo info{}; + info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + info.setLayoutCount = static_cast(Sets.size()); + info.pSetLayouts = Sets.data(); + info.pushConstantRangeCount = static_cast(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 diff --git a/rendering/vulkan14/vulkan14descriptors.h b/rendering/vulkan14/vulkan14descriptors.h new file mode 100644 index 00000000..8e3b52fd --- /dev/null +++ b/rendering/vulkan14/vulkan14descriptors.h @@ -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 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 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 const &Sets, std::vector const &PushConstants, std::string const &Name); + +} // namespace vk14 diff --git a/rendering/vulkan14/vulkan14device.cpp b/rendering/vulkan14/vulkan14device.cpp index 2f0822b2..a6eed96f 100644 --- a/rendering/vulkan14/vulkan14device.cpp +++ b/rendering/vulkan14/vulkan14device.cpp @@ -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{}; diff --git a/rendering/vulkan14/vulkan14device.h b/rendering/vulkan14/vulkan14device.h index 17a9e642..7aaae3d0 100644 --- a/rendering/vulkan14/vulkan14device.h +++ b/rendering/vulkan14/vulkan14device.h @@ -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}; diff --git a/rendering/vulkan14/vulkan14imgui.cpp b/rendering/vulkan14/vulkan14imgui.cpp new file mode 100644 index 00000000..57476cfc --- /dev/null +++ b/rendering/vulkan14/vulkan14imgui.cpp @@ -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 + +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(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(Width)}, height{static_cast(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 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::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(); + if (false == target->create(*m_resources->memory, desc, "ui image")) + { + return Id; + } + } + if (false == m_resources->uploads->upload(target, Rgba, static_cast(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(offsetof(ImDrawVert, pos))}, + VkVertexInputAttributeDescription{1, 0, VK_FORMAT_R32G32_SFLOAT, static_cast(offsetof(ImDrawVert, uv))}, + VkVertexInputAttributeDescription{2, 0, VK_FORMAT_R8G8B8A8_UNORM, static_cast(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(drawdata->TotalVtxCount) * sizeof(ImDrawVert)}; + auto const indexbytes{static_cast(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(Extent.width))}; + auto const maxy{std::min((command.ClipRect.w - clipoffset.y) * scale.y, static_cast(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(minx), static_cast(miny)}, {static_cast(maxx - minx), static_cast(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(command.VtxOffset + vertexoffset), 0); + } + vertexoffset += static_cast(commands->VtxBuffer.Size); + indexoffset += static_cast(commands->IdxBuffer.Size); + } +} + +} // namespace vk14 diff --git a/rendering/vulkan14/vulkan14imgui.h b/rendering/vulkan14/vulkan14imgui.h new file mode 100644 index 00000000..7a4238dd --- /dev/null +++ b/rendering/vulkan14/vulkan14imgui.h @@ -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 m_vertex; + std::shared_ptr m_fragment; + std::uint64_t m_font{0}; + ImDrawData *m_drawdata{nullptr}; + // the images made by Create_Image() + std::vector m_images; +}; + +} // namespace vk14 diff --git a/rendering/vulkan14/vulkan14instance.cpp b/rendering/vulkan14/vulkan14instance.cpp index b5ff6cd9..641f0018 100644 --- a/rendering/vulkan14/vulkan14instance.cpp +++ b/rendering/vulkan14/vulkan14instance.cpp @@ -249,6 +249,7 @@ bool instance::collect_extensions(std::vector &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 &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 diff --git a/rendering/vulkan14/vulkan14memory.cpp b/rendering/vulkan14/vulkan14memory.cpp new file mode 100644 index 00000000..3155bc54 --- /dev/null +++ b/rendering/vulkan14/vulkan14memory.cpp @@ -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 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(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(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 &&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(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 diff --git a/rendering/vulkan14/vulkan14memory.h b/rendering/vulkan14/vulkan14memory.h new file mode 100644 index 00000000..01e15b2a --- /dev/null +++ b/rendering/vulkan14/vulkan14memory.h @@ -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 +#include +#include + +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 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 &&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>> 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 diff --git a/rendering/vulkan14/vulkan14passes.cpp b/rendering/vulkan14/vulkan14passes.cpp new file mode 100644 index 00000000..316ab101 --- /dev/null +++ b/rendering/vulkan14/vulkan14passes.cpp @@ -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 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 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(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 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 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(frame.used); + ++frame.used; + } + else + { + m_open = static_cast(max_passes); + } +} + +void pass_list::end_pass() +{ + if (m_open < 0) + { + return; + } + if (m_open < static_cast(max_passes)) + { + vkCmdWriteTimestamp2(m_commands, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, m_frames[m_frame].pool, static_cast(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 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(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 diff --git a/rendering/vulkan14/vulkan14passes.h b/rendering/vulkan14/vulkan14passes.h new file mode 100644 index 00000000..74fed9d1 --- /dev/null +++ b/rendering/vulkan14/vulkan14passes.h @@ -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 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 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 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 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> m_times; + double m_framems{0.0}; +}; + +} // namespace vk14 diff --git a/rendering/vulkan14/vulkan14renderer.cpp b/rendering/vulkan14/vulkan14renderer.cpp index 24e9b478..0fed2880 100644 --- a/rendering/vulkan14/vulkan14renderer.cpp +++ b/rendering/vulkan14/vulkan14renderer.cpp @@ -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 textures; // [0]: the main window, then the windows of the extra viewports std::vector> windows; std::vector 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(frames[i].uploads), "uploads " + std::to_string(i)); instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast(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(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([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::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()}; @@ -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 pending; + { + std::lock_guard 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 waits; std::vector 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 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(waits.size()); submit.pWaitSemaphoreInfos = waits.data(); - submit.commandBufferInfoCount = 1; - submit.pCommandBufferInfos = &commands; + submit.commandBufferInfoCount = static_cast(commands.size()); + submit.pCommandBufferInfos = commands.data(); submit.signalSemaphoreInfoCount = static_cast(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 vulkan14_renderer::Fetch_Shader(std::string const &name) +std::shared_ptr 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(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() {} diff --git a/rendering/vulkan14/vulkan14renderer.h b/rendering/vulkan14/vulkan14renderer.h index 0c118882..4ef2a240 100644 --- a/rendering/vulkan14/vulkan14renderer.h +++ b/rendering/vulkan14/vulkan14renderer.h @@ -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 Fetch_Shader(std::string const &name) override; + std::shared_ptr 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 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}; diff --git a/rendering/vulkan14/vulkan14shaders.cpp b/rendering/vulkan14/vulkan14shaders.cpp new file mode 100644 index 00000000..1b013c2d --- /dev/null +++ b/rendering/vulkan14/vulkan14shaders.cpp @@ -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 +#include +#include + +#include +#include +#include +#include +#include + +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 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(file.tellg())}; + if ((size < 20) || (size % 4 != 0)) + { + return {}; + } + std::vector words(size / 4); + file.seekg(0); + file.read(reinterpret_cast(words.data()), static_cast(size)); + if ((false == static_cast(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(Data), static_cast(Size)); + if (false == static_cast(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(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(Data)}; + for (std::size_t i = 0; i < Size; ++i) + { + Hash ^= bytes[i]; + Hash *= 1099511628211ull; + } +} + +template 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 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_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()}; + 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 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(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 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 data; + { + std::ifstream file(m_cachepath, std::ios::binary | std::ios::ate); + if (file.is_open()) + { + data.resize(static_cast(file.tellg())); + file.seekg(0); + file.read(data.data(), static_cast(data.size())); + if (false == static_cast(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 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 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 entries(Desc.specialization.size()); + for (std::size_t i = 0; i < entries.size(); ++i) + { + entries[i] = {static_cast(i), static_cast(i * sizeof(std::uint32_t)), sizeof(std::uint32_t)}; + } + VkSpecializationInfo specialization{}; + specialization.mapEntryCount = static_cast(entries.size()); + specialization.pMapEntries = entries.data(); + specialization.dataSize = Desc.specialization.size() * sizeof(std::uint32_t); + specialization.pData = Desc.specialization.data(); + + std::vector modules(Desc.stages.size()); + std::vector 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(Desc.vertices.bindings.size()); + vertexinput.pVertexBindingDescriptions = Desc.vertices.bindings.data(); + vertexinput.vertexAttributeDescriptionCount = static_cast(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 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(attachments.size()); + blend.pAttachments = attachments.data(); + + VkPipelineDynamicStateCreateInfo dynamic{}; + dynamic.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynamic.dynamicStateCount = static_cast(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(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(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(std::chrono::steady_clock::now() - start).count(); + m_pipelines.emplace(key, pipeline); + return pipeline; +} + +VkPipeline pipeline_library::compute(std::shared_ptr const &Shader, VkPipelineLayout Layout, std::vector 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 entries(Specialization.size()); + for (std::size_t i = 0; i < entries.size(); ++i) + { + entries[i] = {static_cast(i), static_cast(i * sizeof(std::uint32_t)), sizeof(std::uint32_t)}; + } + VkSpecializationInfo specialization{}; + specialization.mapEntryCount = static_cast(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(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(Extent.width), static_cast(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 diff --git a/rendering/vulkan14/vulkan14shaders.h b/rendering/vulkan14/vulkan14shaders.h new file mode 100644 index 00000000..a0c358cd --- /dev/null +++ b/rendering/vulkan14/vulkan14shaders.h @@ -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 +#include + +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 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 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 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> 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 bindings; + std::vector 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> 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 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 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 const &Shader, VkPipelineLayout Layout, std::vector 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 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 diff --git a/rendering/vulkan14/vulkan14swapchain.cpp b/rendering/vulkan14/vulkan14swapchain.cpp index e44c88cb..33258290 100644 --- a/rendering/vulkan14/vulkan14swapchain.cpp +++ b/rendering/vulkan14/vulkan14swapchain.cpp @@ -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() diff --git a/rendering/vulkan14/vulkan14swapchain.h b/rendering/vulkan14/vulkan14swapchain.h index 1513693a..89ab00d6 100644 --- a/rendering/vulkan14/vulkan14swapchain.h +++ b/rendering/vulkan14/vulkan14swapchain.h @@ -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 m_images; + // the states of the images for the barriers of the passes + std::vector m_states; std::vector m_views; // one per image: a semaphore given to the presentation can be reused only once the image comes back std::vector m_presentsemaphores; diff --git a/rendering/vulkan14/vulkan14texture.cpp b/rendering/vulkan14/vulkan14texture.cpp new file mode 100644 index 00000000..d98cc48e --- /dev/null +++ b/rendering/vulkan14/vulkan14texture.cpp @@ -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 + +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 const &Image, void const *Data, VkDeviceSize Size, std::vector const &Levels, bool const Mipmaps) +{ + job upload; + upload.target = Image; + upload.levels = Levels; + upload.mipmaps = Mipmaps; + upload.staging = std::make_shared(); + 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(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 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(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(regions.size()); + copy.pRegions = regions.data(); + vkCmdCopyBufferToImage2(commands, ©); + + auto const given{static_cast(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::max(extent.width >> (level - 1), 1u)), static_cast(std::max(extent.height >> (level - 1), 1u)), 1}; + blit.dstSubresource = {aspect, level, 0, layers}; + blit.dstOffsets[1] = {static_cast(std::max(extent.width >> level, 1u)), static_cast(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 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 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 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 levels; + std::vector 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((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(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(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::floor(std::log2(std::max(data_width, data_height)))) + 1 : static_cast(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()}; + 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(); + 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(); + 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 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 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 pending; + std::size_t width{0}, height{0}; + { + std::lock_guard 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(Width)}, height{static_cast(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::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(); + 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(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 diff --git a/rendering/vulkan14/vulkan14texture.h b/rendering/vulkan14/vulkan14texture.h new file mode 100644 index 00000000..8b7b834e --- /dev/null +++ b/rendering/vulkan14/vulkan14texture.h @@ -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 +#include +#include + +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 const &Image, void const *Data, VkDeviceSize Size, std::vector 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 target; + std::shared_ptr staging; + std::vector 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 m_pending; + std::uint64_t m_uploaded{0}; + mutable std::unordered_map m_blittable; +}; + +// the images the shaders can be given by an id +class image_table +{ + public: + struct entry + { + std::shared_ptr target; + VkSampler sampler{VK_NULL_HANDLE}; + }; + // a new id for the image (0 is no image) + std::uint64_t add(std::shared_ptr const &Image, VkSampler Sampler); + void replace(std::uint64_t Id, std::shared_ptr 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 m_entries{entry{}}; + std::vector 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 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(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 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 m_pending; + std::size_t m_pendingwidth{0}; + std::size_t m_pendingheight{0}; +}; + +} // namespace vk14 diff --git a/rendering/vulkan14/vulkan14vma.cpp b/rendering/vulkan14/vulkan14vma.cpp new file mode 100644 index 00000000..cbc6b54a --- /dev/null +++ b/rendering/vulkan14/vulkan14vma.cpp @@ -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 diff --git a/scripting/pythonscreenviewer.h b/scripting/pythonscreenviewer.h index ccd78f0e..185afe92 100644 --- a/scripting/pythonscreenviewer.h +++ b/scripting/pythonscreenviewer.h @@ -1,4 +1,6 @@ #include "rendering/renderer.h" +#include "gl/shader.h" +#include "gl/ubo.h" #include "scripting/PyInt.h" #include diff --git a/shaders/README.md b/shaders/README.md new file mode 100644 index 00000000..2ee93eeb --- /dev/null +++ b/shaders/README.md @@ -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 `). 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). diff --git a/shaders/alphashadowmap.frag b/shaders/alphashadowmap.frag deleted file mode 100644 index 9ff0e1d7..00000000 --- a/shaders/alphashadowmap.frag +++ /dev/null @@ -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; -} diff --git a/shaders/billboard.frag b/shaders/billboard.frag deleted file mode 100644 index 7ef0756f..00000000 --- a/shaders/billboard.frag +++ /dev/null @@ -1,25 +0,0 @@ -in vec2 f_coord; - -#texture (tex1, 0, sRGB_A) -uniform sampler2D tex1; - -#include -#include - -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 -} diff --git a/shaders/color.frag b/shaders/color.frag deleted file mode 100644 index 3f1d9308..00000000 --- a/shaders/color.frag +++ /dev/null @@ -1,26 +0,0 @@ -in vec3 f_color; -in vec4 f_pos; - -#include -#include -#include - -layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED -layout(location = 1) out vec4 out_motion; -#endif - -#include - -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 -} diff --git a/shaders/compile_spirv.py b/shaders/compile_spirv.py new file mode 100644 index 00000000..407bac8b --- /dev/null +++ b/shaders/compile_spirv.py @@ -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 ] [--only ogl46|vk14] [] +# +# 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()) diff --git a/shaders/copy.frag b/shaders/copy.frag deleted file mode 100644 index d1cdb753..00000000 --- a/shaders/copy.frag +++ /dev/null @@ -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); -} diff --git a/shaders/envmapping.glsl b/shaders/envmapping.glsl deleted file mode 100644 index 880f6913..00000000 --- a/shaders/envmapping.glsl +++ /dev/null @@ -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 -} diff --git a/shaders/map_poi.frag b/shaders/map_poi.frag deleted file mode 100644 index cb8c43ea..00000000 --- a/shaders/map_poi.frag +++ /dev/null @@ -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; -} diff --git a/shaders/mat_default_0.frag b/shaders/mat_default_0.frag deleted file mode 100644 index f951f70d..00000000 --- a/shaders/mat_default_0.frag +++ /dev/null @@ -1 +0,0 @@ -#include diff --git a/shaders/mat_default_1.frag b/shaders/mat_default_1.frag deleted file mode 100644 index 8eb248b7..00000000 --- a/shaders/mat_default_1.frag +++ /dev/null @@ -1 +0,0 @@ -#include diff --git a/shaders/mat_default_2.frag b/shaders/mat_default_2.frag deleted file mode 100644 index 9761f4fe..00000000 --- a/shaders/mat_default_2.frag +++ /dev/null @@ -1 +0,0 @@ -#include diff --git a/shaders/mat_detail_parallax.frag b/shaders/mat_detail_parallax.frag deleted file mode 100644 index 185368f1..00000000 --- a/shaders/mat_detail_parallax.frag +++ /dev/null @@ -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 - -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 -#include -#include - -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 -} diff --git a/shaders/mat_detail_parallax_specgloss.frag b/shaders/mat_detail_parallax_specgloss.frag deleted file mode 100644 index 7ac5a4c2..00000000 --- a/shaders/mat_detail_parallax_specgloss.frag +++ /dev/null @@ -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 - -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 -#include -#include - -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 -} diff --git a/shaders/mat_invalid.frag b/shaders/mat_invalid.frag deleted file mode 100644 index 4a94ae47..00000000 --- a/shaders/mat_invalid.frag +++ /dev/null @@ -1,20 +0,0 @@ -#include -#include - -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 -} diff --git a/shaders/mat_parallax.frag b/shaders/mat_parallax.frag deleted file mode 100644 index 2211de23..00000000 --- a/shaders/mat_parallax.frag +++ /dev/null @@ -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 - -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 -#include -#include - -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 -} diff --git a/shaders/mat_parallax_specgloss.frag b/shaders/mat_parallax_specgloss.frag deleted file mode 100644 index 46c7f456..00000000 --- a/shaders/mat_parallax_specgloss.frag +++ /dev/null @@ -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 - -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 -#include -#include - -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 -} diff --git a/shaders/mat_trackblend.frag b/shaders/mat_trackblend.frag deleted file mode 100644 index ea067cec..00000000 --- a/shaders/mat_trackblend.frag +++ /dev/null @@ -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 diff --git a/shaders/mat_trackblend_normalmap.frag b/shaders/mat_trackblend_normalmap.frag deleted file mode 100644 index 468c70cd..00000000 --- a/shaders/mat_trackblend_normalmap.frag +++ /dev/null @@ -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 diff --git a/shaders/mat_trackblend_parallax.frag b/shaders/mat_trackblend_parallax.frag deleted file mode 100644 index 5ee5a465..00000000 --- a/shaders/mat_trackblend_parallax.frag +++ /dev/null @@ -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 diff --git a/shaders/mat_trackblend_specgloss.frag b/shaders/mat_trackblend_specgloss.frag deleted file mode 100644 index 436552e1..00000000 --- a/shaders/mat_trackblend_specgloss.frag +++ /dev/null @@ -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 diff --git a/shaders/ogl46/alphashadowmap.frag b/shaders/ogl46/alphashadowmap.frag new file mode 100644 index 00000000..947e0dc4 --- /dev/null +++ b/shaders/ogl46/alphashadowmap.frag @@ -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; +} diff --git a/shaders/apply_fog.glsl b/shaders/ogl46/apply_fog.glsl similarity index 97% rename from shaders/apply_fog.glsl rename to shaders/ogl46/apply_fog.glsl index 19f59a30..6f057d25 100644 --- a/shaders/apply_fog.glsl +++ b/shaders/ogl46/apply_fog.glsl @@ -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 +#include "fog_volume.glsl" // rgb: light the fog adds, a: what it lets through vec4 fog_at_fragment() diff --git a/shaders/atmosphere_ambient.comp b/shaders/ogl46/atmosphere_ambient.comp similarity index 97% rename from shaders/atmosphere_ambient.comp rename to shaders/ogl46/atmosphere_ambient.comp index a53c964e..10f3836b 100644 --- a/shaders/atmosphere_ambient.comp +++ b/shaders/ogl46/atmosphere_ambient.comp @@ -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 -#include +#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; diff --git a/shaders/atmosphere_common.glsl b/shaders/ogl46/atmosphere_common.glsl similarity index 100% rename from shaders/atmosphere_common.glsl rename to shaders/ogl46/atmosphere_common.glsl diff --git a/shaders/atmosphere_integrate.glsl b/shaders/ogl46/atmosphere_integrate.glsl similarity index 100% rename from shaders/atmosphere_integrate.glsl rename to shaders/ogl46/atmosphere_integrate.glsl diff --git a/shaders/atmosphere_multiscattering.comp b/shaders/ogl46/atmosphere_multiscattering.comp similarity index 95% rename from shaders/atmosphere_multiscattering.comp rename to shaders/ogl46/atmosphere_multiscattering.comp index 9b69abdc..4512a9f9 100644 --- a/shaders/atmosphere_multiscattering.comp +++ b/shaders/ogl46/atmosphere_multiscattering.comp @@ -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 -#include +#include "common.glsl" +#include "atmosphere_common.glsl" layout(binding = ATMOSPHERE_TRANSMITTANCE_UNIT) uniform sampler2D atmo_transmittance_lut; -#include +#include "atmosphere_integrate.glsl" layout(local_size_x = 64) in; diff --git a/shaders/atmosphere_skyview.comp b/shaders/ogl46/atmosphere_skyview.comp similarity index 92% rename from shaders/atmosphere_skyview.comp rename to shaders/ogl46/atmosphere_skyview.comp index 6adb3c62..b7e4b34d 100644 --- a/shaders/atmosphere_skyview.comp +++ b/shaders/ogl46/atmosphere_skyview.comp @@ -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 -#include +#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 +#include "atmosphere_integrate.glsl" layout(local_size_x = 8, local_size_y = 8) in; diff --git a/shaders/atmosphere_transmittance.comp b/shaders/ogl46/atmosphere_transmittance.comp similarity index 93% rename from shaders/atmosphere_transmittance.comp rename to shaders/ogl46/atmosphere_transmittance.comp index cbd5fd81..846fa5bd 100644 --- a/shaders/atmosphere_transmittance.comp +++ b/shaders/ogl46/atmosphere_transmittance.comp @@ -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 -#include +#include "common.glsl" +#include "atmosphere_common.glsl" layout(local_size_x = 8, local_size_y = 8) in; diff --git a/shaders/ogl46/billboard.frag b/shaders/ogl46/billboard.frag new file mode 100644 index 00000000..42f3e64d --- /dev/null +++ b/shaders/ogl46/billboard.frag @@ -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); + } +} diff --git a/shaders/celestial.frag b/shaders/ogl46/celestial.frag similarity index 87% rename from shaders/celestial.frag rename to shaders/ogl46/celestial.frag index eee97554..69c6cf45 100644 --- a/shaders/celestial.frag +++ b/shaders/ogl46/celestial.frag @@ -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 -#include +#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); + } } diff --git a/shaders/celestial.vert b/shaders/ogl46/celestial.vert similarity index 81% rename from shaders/celestial.vert rename to shaders/ogl46/celestial.vert index f11cdb79..26182c09 100644 --- a/shaders/celestial.vert +++ b/shaders/ogl46/celestial.vert @@ -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 +#include "common.glsl" void main() { diff --git a/shaders/clouds_common.glsl b/shaders/ogl46/clouds_common.glsl similarity index 99% rename from shaders/clouds_common.glsl rename to shaders/ogl46/clouds_common.glsl index f55e6d56..3f5cb019 100644 --- a/shaders/clouds_common.glsl +++ b/shaders/ogl46/clouds_common.glsl @@ -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 +#include "weather_common.glsl" layout(binding = CLOUD_SHAPE_TEX) uniform sampler3D cloud_shape_noise; layout(binding = CLOUD_DETAIL_TEX) uniform sampler3D cloud_detail_noise; diff --git a/shaders/clouds_march.comp b/shaders/ogl46/clouds_march.comp similarity index 99% rename from shaders/clouds_march.comp rename to shaders/ogl46/clouds_march.comp index a21f8187..b7038779 100644 --- a/shaders/clouds_march.comp +++ b/shaders/ogl46/clouds_march.comp @@ -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 -#include +#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; diff --git a/shaders/clouds_noise.comp b/shaders/ogl46/clouds_noise.comp similarity index 98% rename from shaders/clouds_noise.comp rename to shaders/ogl46/clouds_noise.comp index 72c7554e..fc7640a9 100644 --- a/shaders/clouds_noise.comp +++ b/shaders/ogl46/clouds_noise.comp @@ -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 -#include +#include "common.glsl" +#include "random.glsl" layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in; diff --git a/shaders/clouds_shadow.comp b/shaders/ogl46/clouds_shadow.comp similarity index 92% rename from shaders/clouds_shadow.comp rename to shaders/ogl46/clouds_shadow.comp index 4e9d53af..4b8d2bb3 100644 --- a/shaders/clouds_shadow.comp +++ b/shaders/ogl46/clouds_shadow.comp @@ -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 -#include +#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; diff --git a/shaders/clouds_temporal.comp b/shaders/ogl46/clouds_temporal.comp similarity index 97% rename from shaders/clouds_temporal.comp rename to shaders/ogl46/clouds_temporal.comp index 241fc53e..e2a17617 100644 --- a/shaders/clouds_temporal.comp +++ b/shaders/ogl46/clouds_temporal.comp @@ -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 +#include "common.glsl" layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in; diff --git a/shaders/cluster_build.comp b/shaders/ogl46/cluster_build.comp similarity index 97% rename from shaders/cluster_build.comp rename to shaders/ogl46/cluster_build.comp index 37efd677..524d10e3 100644 --- a/shaders/cluster_build.comp +++ b/shaders/ogl46/cluster_build.comp @@ -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 +#include "common.glsl" layout(local_size_x = CLUSTER_BUILD_GROUP_SIZE) in; diff --git a/shaders/cluster_cull.comp b/shaders/ogl46/cluster_cull.comp similarity index 99% rename from shaders/cluster_cull.comp rename to shaders/ogl46/cluster_cull.comp index 1754a171..0545540f 100644 --- a/shaders/cluster_cull.comp +++ b/shaders/ogl46/cluster_cull.comp @@ -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 +#include "common.glsl" layout(local_size_x = CLUSTER_CULL_GROUP_SIZE) in; diff --git a/shaders/cluster_heatmap.frag b/shaders/ogl46/cluster_heatmap.frag similarity index 88% rename from shaders/cluster_heatmap.frag rename to shaders/ogl46/cluster_heatmap.frag index 49bf7c20..3016c200 100644 --- a/shaders/cluster_heatmap.frag +++ b/shaders/ogl46/cluster_heatmap.frag @@ -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 +#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; diff --git a/shaders/ogl46/color.frag b/shaders/ogl46/color.frag new file mode 100644 index 00000000..521ac505 --- /dev/null +++ b/shaders/ogl46/color.frag @@ -0,0 +1,30 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 13) in vec3 f_color; +layout(location = 3) in vec4 f_pos; + +#include "common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" + +layout(location = 0) out vec4 out_color; +layout(location = 1) out vec4 out_motion; + +#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)); + } + if (MOTIONBLUR_ENABLED) + { + out_motion = vec4(0.0f); + } +} diff --git a/shaders/ogl46/common.glsl b/shaders/ogl46/common.glsl new file mode 100644 index 00000000..3234ccdb --- /dev/null +++ b/shaders/ogl46/common.glsl @@ -0,0 +1,322 @@ +// what the shaders of the opengl 4.6 renderer share: the settings, the constants of the game, the lights, the clusters, the +// blocks of the scene and of the model being drawn. the constants below are the values of the game (gl/ubo.h and others): +// compiled from the source, the game puts its own values before the source (gl::glsl_common) and they are taken instead; +// the SPIR-V binaries (shaders/ogl46bin) are compiled with the values written here, so they have to stay the same as in the +// game, which checks them when it starts (and doesn't load the binaries when they differ). +// +// the locations of the inputs and outputs between the stages, the same for a name in every shader (SPIR-V links the stages by +// the locations, not by the names): 0 f_normal, 1 f_normal_raw, 2 f_coord, 3 f_pos, 4 f_clip_pos, 5 f_clip_future_pos, +// 6 TangentFragPos, 7 UserData, 8-10 f_tbn, 11 f_model_draw, 12 f_albedo, 13 f_color, 14 f_coords, 15 f_local, 16 f_soft, +// 17 f_streak, 18 g_coord, 19 g_coords, 20 g_pos +#ifndef COMMON_GLSL +#define COMMON_GLSL + +#if !defined(STAGE_VERTEX) && !defined(STAGE_FRAGMENT) && !defined(STAGE_GEOMETRY) && !defined(STAGE_COMPUTE) +#error "the stage of the shader isn't defined: #define STAGE_VERTEX 1 (STAGE_FRAGMENT, STAGE_GEOMETRY, STAGE_COMPUTE) before the include of common.glsl" +#endif + +#include "settings.glsl" + +#ifndef MASZYNA_CONSTANTS +#define MASZYNA_CONSTANTS 1 +#define MAX_LIGHTS 1024U +#define MAX_CASCADES 3U +#define MAX_PARAMS 3U +#define SHADOW_TEX 8 +#define ENV_TEX 9 +#define HEADLIGHT_TEX 10 +#define ENV_GGX_TEX 11 +#define SCENE_UBO_BINDING 0 +#define MODEL_UBO_BINDING 1 +#define INSTANCE_SSBO_BINDING 8 +#define INSTANCE_RECORD_SSBO_BINDING 9 +#define INSTANCE_JOB_SSBO_BINDING 10 +#define INSTANCE_GROUP_SSBO_BINDING 11 +#define INSTANCE_LOD_SSBO_BINDING 12 +#define INSTANCE_COUNTER_SSBO_BINDING 13 +#define INSTANCE_OUTPUT_SSBO_BINDING 14 +#define INSTANCE_COMMAND_SSBO_BINDING 15 +#define MODEL_DRAW_SSBO_BINDING 10 +#define INSTANCE_CULL_GROUP_SIZE 64 +#define INSTANCE_OFFSETS_GROUP_SIZE 1024 +#define INSTANCE_COMMAND_GROUP_SIZE 64 +#define HIZ_GROUP_SIZE 8 +#define SHADOW_FIT_GROUP_SIZE 16 +#define SHADOW_FIT_SSBO_BINDING 9 +#define PARTICLE_GROUP_SIZE 64 +#define PARTICLE_SSBO_BINDING 9 +#define PARTICLE_SOURCE_SSBO_BINDING 10 +#define PARTICLE_OWNER_SSBO_BINDING 11 +#define PARTICLE_OUTPUT_SSBO_BINDING 12 +#define PARTICLE_COMMAND_SSBO_BINDING 13 +#define INSTANCE_COUNTER_HEADER 8 +#define INSTANCE_JOB_SLEEPERS 1U +#define LIGHT_SSBO_BINDING 0 +#define CLUSTER_GRID_X 16 +#define CLUSTER_GRID_Y 9 +#define CLUSTER_GRID_Z 24 +#define CLUSTER_COUNT 3456 +#define MAX_LIGHTS_PER_CLUSTER 64 +#define CLUSTER_BUILD_GROUP_SIZE 64 +#define CLUSTER_CULL_GROUP_SIZE 64 +#define CLUSTER_UBO_BINDING 2 +#define CLUSTER_AABB_SSBO_BINDING 1 +#define CLUSTER_RANGE_SSBO_BINDING 2 +#define CLUSTER_INDEX_SSBO_BINDING 3 +#define CLUSTER_COUNTER_SSBO_BINDING 4 +#define ENVMAP_GGX_LEVELS 7 +#define ENVMAP_GGX_ROUGHNESS_MIN 0.200000 +#define ENVMAP_GGX_GROUP_SIZE 8 +#define SKY_SSBO_BINDING 5 +#define EXPOSURE_SSBO_BINDING 6 +#define EXPOSURE_HISTOGRAM_BINS 256 +#define EXPOSURE_HISTOGRAM_GROUP_SIZE 16 +#define ATMOSPHERE_UBO_BINDING 4 +#define ATMOSPHERE_RESULT_SSBO_BINDING 7 +#define ATMOSPHERE_TRANSMITTANCE_WIDTH 256 +#define ATMOSPHERE_TRANSMITTANCE_HEIGHT 64 +#define ATMOSPHERE_MULTISCATTERING_SIZE 32 +#define ATMOSPHERE_SKYVIEW_WIDTH 192 +#define ATMOSPHERE_SKYVIEW_HEIGHT 108 +#define ATMOSPHERE_AMBIENT_GROUP_SIZE 256 +#define ATMOSPHERE_TRANSMITTANCE_UNIT 0 +#define ATMOSPHERE_MULTISCATTERING_UNIT 1 +#define ATMOSPHERE_SKYVIEW_UNIT 2 +#define SKY_VIEW_TEX 12 +#define LOCAL_SHADOW_TEX 13 +#define LOCAL_SHADOW_LAMPS_TEX 14 +#define HEADLIGHT_SHADOW_TEX 15 +#define MAX_HEADLIGHT_SHADOWS 4U +#define FOG_VOLUME_TEX 16 +#define CLOUD_SHADOW_TEX 17 +#define CLOUD_TEX 18 +#define CLOUD_SHAPE_TEX 19 +#define CLOUD_DETAIL_TEX 20 +#define CLOUD_COVERAGE_TEX 21 +#define CLOUD_HISTORY_TEX 22 +#define SCENE_DEPTH_TEX 23 +#define WEATHER_UBO_BINDING 5 +#define WEATHER_SCRATCH_SSBO_BINDING 9 +#define MAX_CLOUD_LAYERS 4 +#define FOG_HORIZON_SAMPLES 32 +#define WEATHER_GROUP_SIZE 8 +#define FOG_VOLUME_BIAS 10.000000 +#endif + +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) +layout(location = 11) flat out uint f_model_draw; +#define MODEL_DRAW gl_DrawID +#elif defined(STAGE_FRAGMENT) +layout(location = 11) 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, binding = MODEL_UBO_BINDING) 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, binding = SCENE_UBO_BINDING) 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 + +#endif diff --git a/shaders/conversion.glsl b/shaders/ogl46/conversion.glsl similarity index 95% rename from shaders/conversion.glsl rename to shaders/ogl46/conversion.glsl index 33784a75..d0f2157c 100644 --- a/shaders/conversion.glsl +++ b/shaders/ogl46/conversion.glsl @@ -1,108 +1,108 @@ -vec3 rgb2yuv(vec3 val) -{ - return - clamp( - vec3( - (0.257 * val.r) + (0.504 * val.g) + (0.098 * val.b) + 0.0625, - -(0.148 * val.r) - (0.291 * val.g) + (0.439 * val.b) + 0.5, - (0.439 * val.r) - (0.368 * val.g) - (0.071 * val.b) + 0.5 ), - 0.0, 1.0); -} - -vec3 yuv2rgb(vec3 val) -{ - val -= vec3(0.0625, 0.5, 0.5); - return - clamp( - vec3( - (1.164 * val.r) + (1.596 * val.b), - (1.164 * val.r) - (0.391 * val.g) - (0.813 * val.b), - (1.164 * val.r) + (2.018 * val.g) ), - 0.0, 1.0); -} - -vec3 rgb2hsl( vec3 col ) -{ - float red = col.r; - float green = col.g; - float blue = col.b; - - float minc = min( col.r, min( col.g, col.b )); - float maxc = max( col.r, max( col.g, col.b )); - float delta = maxc - minc; - - float lum = (minc + maxc) * 0.5; - float sat = 0.0; - float hue = 0.0; - - if (lum > 0.0 && lum < 1.0) { - float mul = (lum < 0.5) ? (lum) : (1.0-lum); - sat = delta / (mul * 2.0); - } - - vec3 masks = vec3( - (maxc == red && maxc != green) ? 1.0 : 0.0, - (maxc == green && maxc != blue) ? 1.0 : 0.0, - (maxc == blue && maxc != red) ? 1.0 : 0.0 - ); - - vec3 adds = vec3( - ((green - blue ) / delta), - 2.0 + ((blue - red ) / delta), - 4.0 + ((red - green) / delta) - ); - - float deltaGtz = (delta > 0.0) ? 1.0 : 0.0; - - hue += dot( adds, masks ); - hue *= deltaGtz; - hue /= 6.0; - - if (hue < 0.0) - hue += 1.0; - - return vec3( hue, sat, lum ); -} - -vec3 hsl2rgb( vec3 col ) -{ - const float onethird = 1.0 / 3.0; - const float twothird = 2.0 / 3.0; - const float rcpsixth = 6.0; - - float hue = col.x; - float sat = col.y; - float lum = col.z; - - vec3 xt = vec3( - rcpsixth * (hue - twothird), - 0.0, - rcpsixth * (1.0 - hue) - ); - - if (hue < twothird) { - xt.r = 0.0; - xt.g = rcpsixth * (twothird - hue); - xt.b = rcpsixth * (hue - onethird); - } - - if (hue < onethird) { - xt.r = rcpsixth * (onethird - hue); - xt.g = rcpsixth * hue; - xt.b = 0.0; - } - - xt = min( xt, 1.0 ); - - float sat2 = 2.0 * sat; - float satinv = 1.0 - sat; - float luminv = 1.0 - lum; - float lum2m1 = (2.0 * lum) - 1.0; - vec3 ct = (sat2 * xt) + satinv; - - vec3 rgb; - if (lum >= 0.5) { rgb = (luminv * ct) + lum2m1; } - else { rgb = lum * ct; } - - return rgb; -} +vec3 rgb2yuv(vec3 val) +{ + return + clamp( + vec3( + (0.257 * val.r) + (0.504 * val.g) + (0.098 * val.b) + 0.0625, + -(0.148 * val.r) - (0.291 * val.g) + (0.439 * val.b) + 0.5, + (0.439 * val.r) - (0.368 * val.g) - (0.071 * val.b) + 0.5 ), + 0.0, 1.0); +} + +vec3 yuv2rgb(vec3 val) +{ + val -= vec3(0.0625, 0.5, 0.5); + return + clamp( + vec3( + (1.164 * val.r) + (1.596 * val.b), + (1.164 * val.r) - (0.391 * val.g) - (0.813 * val.b), + (1.164 * val.r) + (2.018 * val.g) ), + 0.0, 1.0); +} + +vec3 rgb2hsl( vec3 col ) +{ + float red = col.r; + float green = col.g; + float blue = col.b; + + float minc = min( col.r, min( col.g, col.b )); + float maxc = max( col.r, max( col.g, col.b )); + float delta = maxc - minc; + + float lum = (minc + maxc) * 0.5; + float sat = 0.0; + float hue = 0.0; + + if (lum > 0.0 && lum < 1.0) { + float mul = (lum < 0.5) ? (lum) : (1.0-lum); + sat = delta / (mul * 2.0); + } + + vec3 masks = vec3( + (maxc == red && maxc != green) ? 1.0 : 0.0, + (maxc == green && maxc != blue) ? 1.0 : 0.0, + (maxc == blue && maxc != red) ? 1.0 : 0.0 + ); + + vec3 adds = vec3( + ((green - blue ) / delta), + 2.0 + ((blue - red ) / delta), + 4.0 + ((red - green) / delta) + ); + + float deltaGtz = (delta > 0.0) ? 1.0 : 0.0; + + hue += dot( adds, masks ); + hue *= deltaGtz; + hue /= 6.0; + + if (hue < 0.0) + hue += 1.0; + + return vec3( hue, sat, lum ); +} + +vec3 hsl2rgb( vec3 col ) +{ + const float onethird = 1.0 / 3.0; + const float twothird = 2.0 / 3.0; + const float rcpsixth = 6.0; + + float hue = col.x; + float sat = col.y; + float lum = col.z; + + vec3 xt = vec3( + rcpsixth * (hue - twothird), + 0.0, + rcpsixth * (1.0 - hue) + ); + + if (hue < twothird) { + xt.r = 0.0; + xt.g = rcpsixth * (twothird - hue); + xt.b = rcpsixth * (hue - onethird); + } + + if (hue < onethird) { + xt.r = rcpsixth * (onethird - hue); + xt.g = rcpsixth * hue; + xt.b = 0.0; + } + + xt = min( xt, 1.0 ); + + float sat2 = 2.0 * sat; + float satinv = 1.0 - sat; + float luminv = 1.0 - lum; + float lum2m1 = (2.0 * lum) - 1.0; + vec3 ct = (sat2 * xt) + satinv; + + vec3 rgb; + if (lum >= 0.5) { rgb = (luminv * ct) + lum2m1; } + else { rgb = lum * ct; } + + return rgb; +} diff --git a/shaders/ogl46/copy.frag b/shaders/ogl46/copy.frag new file mode 100644 index 00000000..1c048c6e --- /dev/null +++ b/shaders/ogl46/copy.frag @@ -0,0 +1,13 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 14) in vec2 f_coords; + +#pragma texture(tex1, 0, sRGB) +layout(binding = 0) uniform sampler2D tex1; + +layout(location = 0) out vec4 out_color; + +void main() +{ + out_color = texture(tex1, f_coords); +} diff --git a/shaders/depthprepass.frag b/shaders/ogl46/depthprepass.frag similarity index 53% rename from shaders/depthprepass.frag rename to shaders/ogl46/depthprepass.frag index a5ce35d0..87d0e6f9 100644 --- a/shaders/depthprepass.frag +++ b/shaders/ogl46/depthprepass.frag @@ -1,6 +1,8 @@ +#version 460 core +#define STAGE_FRAGMENT 1 // depth prepass of materials which never discard a fragment (no alpha test) -in vec2 f_coord; +layout(location = 2) in vec2 f_coord; void main() { diff --git a/shaders/depthprepass.vert b/shaders/ogl46/depthprepass.vert similarity index 85% rename from shaders/depthprepass.vert rename to shaders/ogl46/depthprepass.vert index e6a7ed6c..5f756fef 100644 --- a/shaders/depthprepass.vert +++ b/shaders/ogl46/depthprepass.vert @@ -1,3 +1,5 @@ +#version 460 core +#define STAGE_VERTEX 1 // depth prepass of the instances drawn from the gpu culling (opengl46_renderer::Render_Instances_Indirect()). // the position is worked out the way vertex.vert of the materials does it, and declared invariant in both, so the colour // pass which follows gets the same depth for the same surface @@ -5,11 +7,11 @@ layout(location = 0) in vec3 v_vert; layout(location = 2) in vec2 v_coord; -#include +#include "common.glsl" invariant gl_Position; -out vec2 f_coord; +layout(location = 2) out vec2 f_coord; void main() { diff --git a/shaders/depthprepass_alpha.frag b/shaders/ogl46/depthprepass_alpha.frag similarity index 69% rename from shaders/depthprepass_alpha.frag rename to shaders/ogl46/depthprepass_alpha.frag index cdab4c39..3c6e4170 100644 --- a/shaders/depthprepass_alpha.frag +++ b/shaders/ogl46/depthprepass_alpha.frag @@ -1,13 +1,15 @@ +#version 460 core +#define STAGE_FRAGMENT 1 // depth prepass of materials whose colour shader tests the alpha of the diffuse texture against the opacity of the // material (mat_default.frag and the like). the test here is stricter by one step of 8-bit alpha: a fragment which writes // depth here is never discarded by the colour pass, which would leave a hole with nothing drawn behind it -in vec2 f_coord; +layout(location = 2) in vec2 f_coord; -#include +#include "common.glsl" -#texture (diffuse, 0, sRGB_A) -uniform sampler2D diffuse; +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; void main() { diff --git a/shaders/dither.glsl b/shaders/ogl46/dither.glsl similarity index 62% rename from shaders/dither.glsl rename to shaders/ogl46/dither.glsl index 5b26e8eb..8dd5fe9b 100644 --- a/shaders/dither.glsl +++ b/shaders/ogl46/dither.glsl @@ -5,17 +5,22 @@ #ifndef DITHER_GLSL #define DITHER_GLSL +#include "settings.glsl" + vec3 dither_output(vec3 color) { float noise = (fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715)))) - 0.5) / 255.0; -#if OUTPUT_GAMMA - return max(color + noise, vec3(0.0)); -#else - vec3 linear = max(color, vec3(0.0)); - vec3 encoded = mix(linear * 12.92, 1.055 * pow(linear, vec3(1.0 / 2.4)) - 0.055, step(vec3(0.0031308), linear)); - encoded = max(encoded + noise, vec3(0.0)); - return mix(encoded / 12.92, pow((encoded + 0.055) / 1.055, vec3(2.4)), step(vec3(0.04045), encoded)); -#endif + if (OUTPUT_GAMMA) + { + return max(color + noise, vec3(0.0)); + } + else + { + vec3 linear = max(color, vec3(0.0)); + vec3 encoded = mix(linear * 12.92, 1.055 * pow(linear, vec3(1.0 / 2.4)) - 0.055, step(vec3(0.0031308), linear)); + encoded = max(encoded + noise, vec3(0.0)); + return mix(encoded / 12.92, pow((encoded + 0.055) / 1.055, vec3(2.4)), step(vec3(0.04045), encoded)); + } } #endif diff --git a/shaders/envmap_prefilter.comp b/shaders/ogl46/envmap_prefilter.comp similarity index 96% rename from shaders/envmap_prefilter.comp rename to shaders/ogl46/envmap_prefilter.comp index 6e638583..3f23d333 100644 --- a/shaders/envmap_prefilter.comp +++ b/shaders/ogl46/envmap_prefilter.comp @@ -1,15 +1,17 @@ +#version 460 core +#define STAGE_COMPUTE 1 // prefilters the environment map with the GGX lobe for rough reflections (split sum approximation, Karis 2013), one level // of the prefiltered map per dispatch. filtered importance sampling (Krivanek, Colbert 2008): each sample reads the mipmap // of the source whose texels are about as large as the solid angle the sample stands for, so a few dozen samples give a // smooth result. run when an update of the environment map is complete -#include +#include "common.glsl" layout(local_size_x = ENVMAP_GGX_GROUP_SIZE, local_size_y = ENVMAP_GGX_GROUP_SIZE) in; layout(binding = 0, rgba16f) writeonly uniform imageCube prefilter_target; // the plain map with its box filtered mipmaps, on the unit gl::program binds envmap to -uniform samplerCube envmap; +layout(binding = ENV_TEX) uniform samplerCube envmap; // perceptual roughness of the level being written layout(location = 0) uniform float roughness; diff --git a/shaders/ogl46/envmapping.glsl b/shaders/ogl46/envmapping.glsl new file mode 100644 index 00000000..ef9f9535 --- /dev/null +++ b/shaders/ogl46/envmapping.glsl @@ -0,0 +1,73 @@ +layout(binding = ENV_TEX) uniform samplerCube envmap; +// the same map prefiltered with the GGX lobe (envmap_prefilter.comp), used when env_ggx is 1 +layout(binding = ENV_GGX_TEX) uniform samplerCube envmap_ggx; + +vec3 envmap_color( vec3 normal ) +{ + vec3 envcolor; + if (ENVMAP_ENABLED) + { + vec3 refvec = reflect(f_pos.xyz, normal); // view space + refvec = vec3(inv_view * vec4(refvec, 0.0)); // world space + 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 + { + envcolor = vec3(0.5); + } + 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 + } +} + +// 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; + } +} diff --git a/shaders/exposure_average.comp b/shaders/ogl46/exposure_average.comp similarity index 97% rename from shaders/exposure_average.comp rename to shaders/ogl46/exposure_average.comp index 946d5e9e..b63d77ae 100644 --- a/shaders/exposure_average.comp +++ b/shaders/ogl46/exposure_average.comp @@ -1,9 +1,11 @@ +#version 460 core +#define STAGE_COMPUTE 1 // exposure from the luminance histogram (exposure_histogram.comp): the average log2 luminance of the pixels between the // low and high percentile (the darkest and brightest parts of the image do not move the exposure), adapted over time to, // faster towards the brighter. the exposure brings the adapted luminance to the key, within the limits. one work group, // one invocation per bin; the histogram is cleared for the next frame -#include +#include "common.glsl" layout(local_size_x = EXPOSURE_HISTOGRAM_BINS) in; diff --git a/shaders/exposure_histogram.comp b/shaders/ogl46/exposure_histogram.comp similarity index 95% rename from shaders/exposure_histogram.comp rename to shaders/ogl46/exposure_histogram.comp index 981601f3..5f47bee5 100644 --- a/shaders/exposure_histogram.comp +++ b/shaders/ogl46/exposure_histogram.comp @@ -1,7 +1,9 @@ +#version 460 core +#define STAGE_COMPUTE 1 // luminance histogram of the image for the automatic exposure: every second pixel in both directions, binned by the log2 // of its luminance. bin 0 takes the black pixels, which the average leaves out. exposure_average.comp reads and clears it -#include +#include "common.glsl" layout(local_size_x = EXPOSURE_HISTOGRAM_GROUP_SIZE, local_size_y = EXPOSURE_HISTOGRAM_GROUP_SIZE) in; diff --git a/shaders/fog_horizon.comp b/shaders/ogl46/fog_horizon.comp similarity index 94% rename from shaders/fog_horizon.comp rename to shaders/ogl46/fog_horizon.comp index 4c1d2265..744c1579 100644 --- a/shaders/fog_horizon.comp +++ b/shaders/ogl46/fog_horizon.comp @@ -1,9 +1,11 @@ +#version 460 core +#define STAGE_COMPUTE 1 // colours of the fog towards the horizon by the angle from the azimuth of the sun, from the sky-view table of the atmosphere, // written into the weather buffer (weather_common.glsl: fog_horizon). the far fog then has the colour of the sky behind it. // the atmosphere blends into the old sky as opengl46_sky does it, by its share, white balance and brightness -#include -#include +#include "common.glsl" +#include "atmosphere_common.glsl" layout(local_size_x = FOG_HORIZON_SAMPLES) in; diff --git a/shaders/fog_inject.comp b/shaders/ogl46/fog_inject.comp similarity index 92% rename from shaders/fog_inject.comp rename to shaders/ogl46/fog_inject.comp index 1b864811..9dcb0c43 100644 --- a/shaders/fog_inject.comp +++ b/shaders/ogl46/fog_inject.comp @@ -1,3 +1,5 @@ +#version 460 core +#define STAGE_COMPUTE 1 // the fog volume of the main view, first pass: in every cell of a grid laid over the view (slices spaced exponentially in the // depth), how dense the fog is and how much light it scatters towards the camera. the density: the haze, the patches of the // ground fog, the low clouds; the light: the colour the fog has towards the horizon (weather_common.glsl), less what the @@ -8,18 +10,16 @@ // from doesn't depend on the point (the clusters of the middle of the cell) // rgb: light scattered towards the camera per metre, a: extinction per metre -#include -#include +#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; layout(rgba16f, binding = 0) uniform writeonly image3D fog_scattering; layout(binding = CLOUD_HISTORY_TEX) uniform sampler3D fog_history; -#if SHADOWMAP_ENABLED -uniform sampler2DArrayShadow shadowmap; -#endif -uniform sampler2D headlightmap; +layout(binding = SHADOW_TEX) uniform sampler2DArrayShadow shadowmap; +layout(binding = HEADLIGHT_TEX) uniform sampler2D headlightmap; layout(binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_shadowmap; layout(location = 0) uniform ivec3 grid; @@ -81,31 +81,34 @@ float henyey_greenstein(float cosangle, float g) // share of the sunlight the shadow map holds back at a point of the view space, one comparison float sun_shadow(vec3 position) { -#if SHADOWMAP_ENABLED - float span_distance = dot(position, position); - uint cascade; - for (cascade = 0U; cascade < MAX_CASCADES; cascade++) - if (span_distance <= cascade_end[cascade]) - break; - for (; cascade < MAX_CASCADES; cascade++) + if (SHADOWMAP_ENABLED) { - vec4 lightpos = lightview[cascade] * vec4(position, 1.0); - vec3 coords = lightpos.xyz / lightpos.w; - if (all(greaterThanEqual(coords.xy, vec2(0.0))) && all(lessThanEqual(coords.xy, vec2(1.0)))) + float span_distance = dot(position, position); + uint cascade; + for (cascade = 0U; cascade < MAX_CASCADES; cascade++) + if (span_distance <= cascade_end[cascade]) + break; + for (; cascade < MAX_CASCADES; cascade++) { - if (coords.z < 0.0) + vec4 lightpos = lightview[cascade] * vec4(position, 1.0); + vec3 coords = lightpos.xyz / lightpos.w; + if (all(greaterThanEqual(coords.xy, vec2(0.0))) && all(lessThanEqual(coords.xy, vec2(1.0)))) + { + if (coords.z < 0.0) + return 0.0; + return texture(shadowmap, vec4(coords.xy, float(cascade), coords.z + 0.0001 * float(cascade + 1U))); + } + // outside the map: lit, unless the cascades are fitted to the depth of the view (gfx.shadow.sdsm), whose maps cover + // only the area of the surfaces seen; then the first further cascade whose map holds the point + if (shadow_fit.x <= 0.0) return 0.0; - return texture(shadowmap, vec4(coords.xy, float(cascade), coords.z + 0.0001 * float(cascade + 1U))); } - // outside the map: lit, unless the cascades are fitted to the depth of the view (gfx.shadow.sdsm), whose maps cover - // only the area of the surfaces seen; then the first further cascade whose map holds the point - if (shadow_fit.x <= 0.0) - return 0.0; + return 0.0; + } + else + { + return 0.0; } - return 0.0; -#else - return 0.0; -#endif } // light of a lamp or of the beam of headlights at a point of the view space, before the phase diff --git a/shaders/fog_integrate.comp b/shaders/ogl46/fog_integrate.comp similarity index 98% rename from shaders/fog_integrate.comp rename to shaders/ogl46/fog_integrate.comp index f1e462d2..3c616a83 100644 --- a/shaders/fog_integrate.comp +++ b/shaders/ogl46/fog_integrate.comp @@ -1,3 +1,5 @@ +#version 460 core +#define STAGE_COMPUTE 1 // the fog volume of the main view, second pass: the light and extinction of the cells (fog_inject.comp) added up along the rays // of the view, front to back, into what the fog adds and lets through from the camera to the far side of every slice // (apply_fog.glsl). the light of a cell is integrated over its length as Hillaire (2015) does it, so it doesn't grow with @@ -6,8 +8,8 @@ // the blending over frames differs from cell to cell, which the filter evens out. the history keeps the cells unfiltered. // past the last slice one more: the fog all the way through the volume without the low clouds it holds, for the sky -#include -#include +#include "common.glsl" +#include "weather_common.glsl" layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in; diff --git a/shaders/fog_volume.glsl b/shaders/ogl46/fog_volume.glsl similarity index 99% rename from shaders/fog_volume.glsl rename to shaders/ogl46/fog_volume.glsl index b202ace6..6177589f 100644 --- a/shaders/fog_volume.glsl +++ b/shaders/ogl46/fog_volume.glsl @@ -5,7 +5,7 @@ #ifndef FOG_VOLUME_GLSL #define FOG_VOLUME_GLSL -#include +#include "weather_common.glsl" layout(binding = FOG_VOLUME_TEX) uniform sampler3D fog_volume; diff --git a/shaders/freespot.frag b/shaders/ogl46/freespot.frag similarity index 74% rename from shaders/freespot.frag rename to shaders/ogl46/freespot.frag index 65afce92..6812d65b 100644 --- a/shaders/freespot.frag +++ b/shaders/ogl46/freespot.frag @@ -1,16 +1,16 @@ -in vec4 f_pos; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 3) in vec4 f_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include +#include "common.glsl" layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED layout(location = 1) out vec4 out_motion; -#endif -#include -#include +#include "tonemapping.glsl" +#include "apply_fog.glsl" void main() { @@ -23,12 +23,11 @@ void main() vec4 color = vec4(apply_fog(pow(param[0].rgb, vec3(2.2)) * emission), mix(param[0].a, 0.0f, dist * 2.0f)); // vec4 color = vec4(add_fog(param[0].rgb * emission * (1.0 - get_fog_amount()), 1.0), mix(param[0].a, 0.0f, dist * 2.0f)); out_color = FBOUT(color); -#if MOTIONBLUR_ENABLED + 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 } diff --git a/shaders/freespot.vert b/shaders/ogl46/freespot.vert similarity index 65% rename from shaders/freespot.vert rename to shaders/ogl46/freespot.vert index 590520df..1db47a30 100644 --- a/shaders/freespot.vert +++ b/shaders/ogl46/freespot.vert @@ -1,12 +1,14 @@ +#version 460 core +#define STAGE_VERTEX 1 layout(location = 0) in vec3 v_vert; layout(location = 1) in vec3 v_normal; layout(location = 2) in vec2 v_coord; -#include +#include "common.glsl" -out vec4 f_pos; -out vec4 f_clip_pos; -out vec4 f_clip_future_pos; +layout(location = 3) out vec4 f_pos; +layout(location = 4) out vec4 f_clip_pos; +layout(location = 5) out vec4 f_clip_future_pos; void main() { diff --git a/shaders/grass.frag b/shaders/ogl46/grass.frag similarity index 56% rename from shaders/grass.frag rename to shaders/ogl46/grass.frag index cb883ab8..825f25ca 100644 --- a/shaders/grass.frag +++ b/shaders/ogl46/grass.frag @@ -1,21 +1,21 @@ +#version 460 core +#define STAGE_FRAGMENT 1 // grass of the heightmap terrain (opengl46_grass, grass.vert): the blades lit like the rest of the scene -in vec3 f_normal; -in vec4 f_pos; -in vec3 f_albedo; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +layout(location = 0) in vec3 f_normal; +layout(location = 3) in vec4 f_pos; +layout(location = 12) in vec3 f_albedo; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include +#include "common.glsl" layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED layout(location = 1) out vec4 out_motion; -#endif -#include -#include -#include +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" void main() { @@ -25,17 +25,19 @@ void main() vec3 fragcolor = apply_lights(ambient, fragnormal, f_albedo, 0.0, 0.2, shadow_tone); vec4 color = vec4(apply_fog(fragcolor), 1.0); -#if POSTFX_ENABLED - out_color = color; -#else - out_color = tonemap(color); -#endif -#if MOTIONBLUR_ENABLED + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } + 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 } diff --git a/shaders/grass.vert b/shaders/ogl46/grass.vert similarity index 91% rename from shaders/grass.vert rename to shaders/ogl46/grass.vert index 444e5f7e..c5dc00e3 100644 --- a/shaders/grass.vert +++ b/shaders/ogl46/grass.vert @@ -1,3 +1,5 @@ +#version 460 core +#define STAGE_VERTEX 1 // grass of the heightmap terrain (opengl46_grass): a blade made of the numbers of its vertex and its instance. // // the instances of a draw stand in a tile of a chunk. a blade takes its place in the tile from a sequence which spreads @@ -7,43 +9,43 @@ // collapsed into a point outside the view. the vertices go up the blade in pairs, the last one is its tip. // the chunk is drawn relative to its corner: x, z in metres from the corner, y the height as it is -#include +#include "common.glsl" // x, z: corner of the tile in the chunk, metres; y: side of the tile; w: blades of the tile where its densest kind is painted fully -uniform vec4 grass_tile; +layout(location = 0) uniform vec4 grass_tile; // x: the share of the densest kind at the densest place of the tile (the blades of the tile are counted for it); y: segments // of a blade; z, w: number of the tile in the world -uniform vec4 grass_part; +layout(location = 1) uniform vec4 grass_part; // x: side of the chunk, metres; y: cells of its grid along a side; z: grass samples along a side less one -uniform vec4 grass_chunk; +layout(location = 2) uniform vec4 grass_chunk; // the camera relative to the corner of the chunk (y as it is) -uniform vec4 grass_camera; +layout(location = 3) uniform vec4 grass_camera; // x: distance the grass starts to thin out at, for speed; y: distance the fading starts at (gfx.grass.distance); z: where the // grass ends (gfx.grass.fadedistance) -uniform vec4 grass_lod; +layout(location = 4) uniform vec4 grass_lod; // x, y: direction of the wind (x, z of the world); z: its strength, 0..1; w: time, seconds -uniform vec4 grass_wind; +layout(location = 5) uniform vec4 grass_wind; // the kinds of the chunk: colour at the root and at the tip (linear rgb), and height, its variation, width, lean -uniform vec4 grass_base[4]; -uniform vec4 grass_tip[4]; -uniform vec4 grass_shape[4]; +layout(location = 6) uniform vec4 grass_base[4]; +layout(location = 10) uniform vec4 grass_tip[4]; +layout(location = 14) uniform vec4 grass_shape[4]; // share of the blades of each kind against the densest one of the chunk -uniform vec4 grass_share; +layout(location = 18) uniform vec4 grass_share; // the colour of each kind dried out (linear rgb), and how much it follows the seasons -uniform vec4 grass_dry[4]; +layout(location = 19) uniform vec4 grass_dry[4]; // the time of the year: x: how dry the grass is, y: how fresh (spring), z: how grown (share of the height) -uniform vec4 grass_season; +layout(location = 23) uniform vec4 grass_season; layout(binding = 0) uniform sampler2D grass_heights; // heights of the grid points, R32F layout(binding = 1) uniform sampler2D grass_density; // densities of the kinds, RGBA8 layout(binding = 2) uniform sampler2D grass_holes; // holes cut in the ground, R8 -out vec3 f_normal; -out vec4 f_pos; -out vec3 f_albedo; -out vec4 f_clip_pos; -out vec4 f_clip_future_pos; -out vec4 UserData; +layout(location = 0) out vec3 f_normal; +layout(location = 3) out vec4 f_pos; +layout(location = 12) out vec3 f_albedo; +layout(location = 4) out vec4 f_clip_pos; +layout(location = 5) out vec4 f_clip_future_pos; +layout(location = 7) out vec4 UserData; uint hash(uint x) { diff --git a/shaders/gtao_denoise.comp b/shaders/ogl46/gtao_denoise.comp similarity index 97% rename from shaders/gtao_denoise.comp rename to shaders/ogl46/gtao_denoise.comp index f8a32089..e0014ced 100644 --- a/shaders/gtao_denoise.comp +++ b/shaders/ogl46/gtao_denoise.comp @@ -1,3 +1,5 @@ +#version 460 core +#define STAGE_COMPUTE 1 // smooths the ambient occlusion of gtao_main.comp: the average of the 4x4 pixels around each one, which takes out the // repeating 4x4 pattern of the slices, with the pixels at a clearly different distance left out so the occlusion does // not leak across the edges of objects. one invocation per pixel of the half size target diff --git a/shaders/gtao_main.comp b/shaders/ogl46/gtao_main.comp similarity index 99% rename from shaders/gtao_main.comp rename to shaders/ogl46/gtao_main.comp index a925dcd3..bae03d6d 100644 --- a/shaders/gtao_main.comp +++ b/shaders/ogl46/gtao_main.comp @@ -1,3 +1,5 @@ +#version 460 core +#define STAGE_COMPUTE 1 // ground truth ambient occlusion (Jimenez et al. 2016, "Practical realtime strategies for accurate indirect occlusion"; // integration as in Intel XeGTAO) at half the resolution of the image. for every pixel, a few slices through the // hemisphere around the view direction; in each the horizons on both sides are searched in the depth buffer and the diff --git a/shaders/hiddenarea.frag b/shaders/ogl46/hiddenarea.frag similarity index 56% rename from shaders/hiddenarea.frag rename to shaders/ogl46/hiddenarea.frag index 5580e8cf..52c2097d 100644 --- a/shaders/hiddenarea.frag +++ b/shaders/ogl46/hiddenarea.frag @@ -1,15 +1,16 @@ -#include +#version 460 core +#define STAGE_FRAGMENT 1 +#include "common.glsl" layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED layout(location = 1) out vec4 out_motion; -#endif void main() { out_color = vec4(vec3(0.0f), 1.0f); -#if MOTIONBLUR_ENABLED - out_motion = vec4(0.0f); -#endif + if (MOTIONBLUR_ENABLED) + { + out_motion = vec4(0.0f); + } gl_FragDepth = 1.0f; } diff --git a/shaders/hiddenarea.vert b/shaders/ogl46/hiddenarea.vert similarity index 73% rename from shaders/hiddenarea.vert rename to shaders/ogl46/hiddenarea.vert index 460f7745..2a5852ba 100644 --- a/shaders/hiddenarea.vert +++ b/shaders/ogl46/hiddenarea.vert @@ -1,3 +1,5 @@ +#version 460 core +#define STAGE_VERTEX 1 layout(location = 0) in vec3 v_vert; void main() diff --git a/shaders/hiz_build.comp b/shaders/ogl46/hiz_build.comp similarity index 95% rename from shaders/hiz_build.comp rename to shaders/ogl46/hiz_build.comp index e8af8901..fd56a4a5 100644 --- a/shaders/hiz_build.comp +++ b/shaders/ogl46/hiz_build.comp @@ -1,10 +1,12 @@ +#version 460 core +#define STAGE_COMPUTE 1 // one level of the depth pyramid of the occlusion test of the instances (opengl46_hiz): each texel holds the farthest // depth of the texels of the level below it which it covers. the depth is reversed (1 near, 0 far), so the farthest is // the smallest. the first level is made from the resolved depth of the scene, the others from the level below. // a level halves the one below, rounding down; when that one has an odd size, the last row and column of the new level // take its last row and column as well, so that nothing is left out -#include +#include "common.glsl" layout(local_size_x = HIZ_GROUP_SIZE, local_size_y = HIZ_GROUP_SIZE) in; diff --git a/shaders/instance_cull.comp b/shaders/ogl46/instance_cull.comp similarity index 99% rename from shaders/instance_cull.comp rename to shaders/ogl46/instance_cull.comp index 87a8641a..5a6fd6ca 100644 --- a/shaders/instance_cull.comp +++ b/shaders/ogl46/instance_cull.comp @@ -1,3 +1,5 @@ +#version 460 core +#define STAGE_COMPUTE 1 // culls the model instances of the buckets queued in a render pass and assigns them their lod bands, with the tests // opengl46_renderer::Render_Instanced() makes on the cpu. one invocation per record of a queued bucket. the sleepers of the // tracks queued come as buckets too, with the tests of opengl46_renderer::Render_Sleepers(). @@ -12,7 +14,7 @@ // pyramid is hidden behind the second one as well (it only holds nearer depth), so each instance is drawn at most once, // and everything left out is hidden behind something drawn -#include +#include "common.glsl" layout(local_size_x = INSTANCE_CULL_GROUP_SIZE) in; diff --git a/shaders/instance_cull_commands.comp b/shaders/ogl46/instance_cull_commands.comp similarity index 97% rename from shaders/instance_cull_commands.comp rename to shaders/ogl46/instance_cull_commands.comp index 627cf64a..bec9922e 100644 --- a/shaders/instance_cull_commands.comp +++ b/shaders/ogl46/instance_cull_commands.comp @@ -1,3 +1,5 @@ +#version 460 core +#define STAGE_COMPUTE 1 // fills in the instances of the indirect draw commands of the entries, after instance_cull.comp wrote the modelview // matrices of the instances drawn. one invocation per command. the commands are kept three times over: the first copy // draws the instances of stage 1, the second all of them, the third those of stage 2 (two phases), each from its first @@ -5,7 +7,7 @@ // cursor of its entry, the base instance the first slot of the entry. stage 1 (after stage 2): the other two copies take // the command of the first one, the second with the instances of both stages, the third with the ones past the first copy -#include +#include "common.glsl" layout(local_size_x = INSTANCE_COMMAND_GROUP_SIZE) in; diff --git a/shaders/instance_cull_offsets.comp b/shaders/ogl46/instance_cull_offsets.comp similarity index 96% rename from shaders/instance_cull_offsets.comp rename to shaders/ogl46/instance_cull_offsets.comp index c42094ef..8887b845 100644 --- a/shaders/instance_cull_offsets.comp +++ b/shaders/ogl46/instance_cull_offsets.comp @@ -1,9 +1,11 @@ +#version 460 core +#define STAGE_COMPUTE 1 // turns the counts of instances of the entries, written by stage 0 of instance_cull.comp, into the first slots of the // entries in the output (cursors, which stages 1 and 2 advance as they write, and the same first slots kept for // instance_cull_commands.comp), and puts the total in the header. // a single work group: each invocation sums a run of the entries, then the sums are scanned in shared memory -#include +#include "common.glsl" layout(local_size_x = INSTANCE_OFFSETS_GROUP_SIZE) in; diff --git a/shaders/light_common.glsl b/shaders/ogl46/light_common.glsl similarity index 89% rename from shaders/light_common.glsl rename to shaders/ogl46/light_common.glsl index 817fa969..d4bc4c52 100644 --- a/shaders/light_common.glsl +++ b/shaders/ogl46/light_common.glsl @@ -1,800 +1,801 @@ -#if SHADOWMAP_ENABLED -uniform sampler2DArrayShadow shadowmap; -#endif -uniform sampler2D headlightmap; -// cube shadow maps of the lamps, see gl::LOCAL_SHADOW_TEX and opengl46_local_shadows -layout(binding = LOCAL_SHADOW_TEX) uniform samplerCubeArrayShadow local_shadowmap; -// the same for the lamps of scenery models, the lights with linear 1 -layout(binding = LOCAL_SHADOW_LAMPS_TEX) uniform samplerCubeArrayShadow local_shadowmap_lamps; -// shadow maps of the headlights of vehicles, see gl::HEADLIGHT_SHADOW_TEX and gl::scene_ubs::headlight_shadow -layout(binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_shadowmap; -// depth of the opaque scene drawn before the colour, for the contact shadows (gl::scene_ubs::contact_shadow_params) -layout(binding = SCENE_DEPTH_TEX) uniform sampler2D scene_depth; - -#include -#include -#include - -float glossiness = 1.0; -float metalic = 0.0; -in vec4 UserData; -#param (weathering, 2, 3, 1, zero) - -// --------------------------------------------------------------------- -// Lighting balance tunables - tweak these to control overall scene -// exposure without touching tonemapping.glsl. -// -// AMBIENT_SCALE: brightness of SHADED faces (indirect/sky term). -// Lower -> deeper shadows, less burn under bright -// textures. Higher -> flatter / brighter shading. -// -// SUN_DIFFUSE_SCALE: brightness of UNSHADED (sun-lit) faces. Lower -// this to dim hot surfaces in direct sunlight -// without affecting shaded areas. Was 3.5; 2.5 -// calmly fits the ACES tonemap shoulder. -// -// SUN_NDOTL_SHARPNESS: N.L curve on the sun. 1.0 = pure Lambert, higher -// = sharper terminator (more contrast between -// lit and shaded faces of the same surface). -// --------------------------------------------------------------------- -const float AMBIENT_SCALE = 0.65; -const float SUN_DIFFUSE_SCALE = 1.5; -const float SUN_NDOTL_SHARPNESS = 1.25; - -float length2(vec3 v) -{ - return dot(v, v); -} - -// how many more lamps and headlights the fragment traces contact shadows to, see add_headlights() -int contact_shadow_budget = 0; - -// contact shadow of a light in direction Light_dir (view space, towards the light), 0..1: whether the depth of the scene drawn -// before the colour (scene_depth) holds a surface between the fragment and a point up to Length metres towards the light. it -// catches what the shadow maps are too coarse for (rails on the sleepers, bolts, edges of panels, where a thing stands on the -// ground), and things too small or near for them. only what is in the depth casts it, and only within the image. -// the ray starts a pixel and a half off the visible side of the surface: the depth there, sampled anywhere within a pixel of -// the projected point, then stays behind the ray on a flat surface, so it doesn't shadow itself. a surface nearer than the -// ray by more than the thickness is taken for something in front of the ray, not something it goes through. -// the shadow fades over the last part of the ray (it doesn't end with an edge) and over the last quarter of the range -float contact_shadow(vec3 light_dir, float raylength, float steps) -{ - float range = contact_shadow_params.y; - vec3 position = f_pos.xyz; - float fade = 1.0 - smoothstep(range * 0.75, range, length(position)); - if ((fade <= 0.0) || (raylength <= 0.0) || (steps < 1.0)) - return 0.0; - vec3 normal = normalize(f_normal); - if (dot(normal, position) > 0.0) - normal = -normal; - if (dot(normal, light_dir) <= 0.0) - return 0.0; - vec2 size = vec2(textureSize(scene_depth, 0)); - // the projection is perspective with the depth n / w (reverse z, infinite far plane): w is the distance along the axis - // of the view, and a depth d is a surface at n / d - float near = projection[3][2]; - float distance = (projection * vec4(position, 1.0)).w; - float pixel = 2.0 * distance / (abs(projection[1][1]) * size.y); - vec4 start = projection * vec4(position + normal * (pixel * 1.5), 1.0); - vec4 ray = projection * vec4(light_dir * raylength, 0.0); - float thickness = 0.3 + pixel * 4.0; - float bias = pixel * 0.5; - // a different start along the ray in neighbouring pixels: the steps give noise instead of bands - float jitter = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715)))); - for (float i = 0.0; i < steps; i += 1.0) - { - float t = (i + jitter) / steps; - vec4 clip = start + ray * t; - if (clip.w <= near) - break; - vec2 coords = (clip.xy / clip.w) * 0.5 + 0.5; - if (any(lessThan(coords, vec2(0.0))) || any(greaterThan(coords, vec2(1.0)))) - break; - float depth = texelFetch(scene_depth, min(ivec2(coords * size), ivec2(size) - 1), 0).r; - // nothing drawn there (the sky) - if (depth <= 0.0) - continue; - float nearer = clip.w - near / depth; - if ((nearer > bias) && (nearer < thickness)) - return fade * (1.0 - smoothstep(0.6, 1.0, t)); - } - return 0.0; -} - -// contact shadow of a lamp or headlight Distance metres away, whose light reaches the fragment with Strength, for the first -// few lamps of a fragment (contact_shadow_lamps.x) which light it noticeably. the ray goes at most half way to the light, -// so the lamp doesn't shadow with its own fitting -float contact_shadow_lamp(vec3 light_dir, float distance, float strength) -{ - if ((contact_shadow_budget <= 0) || (strength < 0.005)) - return 0.0; - --contact_shadow_budget; - return contact_shadow(light_dir, min(contact_shadow_params.x, distance * 0.5), contact_shadow_params.w); -} - -// share of the sun (the moon) held back before the fragment by the shadow map and the contact shadow, 0..1 -float calc_shadow_map(); -float calc_shadow() -{ - float shadow = calc_shadow_map(); - if ((contact_shadow_params.x > 0.0) && (shadow < 1.0)) - shadow = max(shadow, contact_shadow(normalize(-lights[0].dir), contact_shadow_params.x, contact_shadow_params.z)); - return shadow; -} - -float calc_shadow_map() -{ -#if SHADOWMAP_ENABLED - float distance = dot(f_pos.xyz, f_pos.xyz); - uint cascade; - for (cascade = 0U; cascade < MAX_CASCADES; cascade++) - if (distance <= cascade_end[cascade]) - break; - // cascades fitted to the depth of the view of an earlier frame (gfx.shadow.sdsm) cover only the area of the points seen - // then: a point at the edge of the view or one which came closer since may be outside the map of its cascade. it goes to - // the first further cascade whose map holds it with the reach of the filter, and is lit past the last one - if (shadow_fit.x > 0.0) - { - float border = 4.0 / float(textureSize(shadowmap, 0).x); - for (; cascade < MAX_CASCADES; cascade++) - { - vec2 fitcoords = (lightview[cascade] * vec4(f_pos.xyz, 1.0)).xy; - if (all(greaterThanEqual(fitcoords, vec2(border))) && all(lessThanEqual(fitcoords, vec2(1.0 - border)))) - break; - } - if (cascade >= MAX_CASCADES) - return 0.0; - } - // Normal offset: sample the shadow map from a point pushed along the surface normal - // by a fraction of the cascade's texel footprint. A constant depth bias can't keep up - // with large texels (far cascades, small shadow maps) on surfaces at a grazing angle - // to the sun, which then show shadow acne stripes. lightview maps view space metres - // to [0,1] shadow map coordinates, so its x/y row lengths give texels per metre. - vec3 shadownormal = normalize(f_normal); - float shadowNdotL = clamp(dot(shadownormal, normalize(-lights[0].dir)), 0.0, 1.0); - mat4 shadowview = lightview[cascade]; - float texelspermetre = min(length(vec3(shadowview[0][0], shadowview[1][0], shadowview[2][0])), - length(vec3(shadowview[0][1], shadowview[1][1], shadowview[2][1]))) * float(textureSize(shadowmap, 0).x); - float texelsize = 1.0 / texelspermetre; - vec3 shadowpos = f_pos.xyz + shadownormal * (texelsize * 1.5 * (1.0 - shadowNdotL)); - vec4 lightpos = shadowview * vec4(shadowpos, 1.0); - vec3 coords = lightpos.xyz / lightpos.w; - if (coords.z < 0.0) - return 0.0f; - - - - float bias = 0.00005f * float(cascade + 1U); - - // Fast path: 4 textureGather() calls cover a 4x4 texel block. Raw gathered - // comparisons must be weighted by the sub-texel position (bilinear weights), - // otherwise the result is constant across each shadow map texel and shadow - // edges turn into texel-sized stair steps. With per-axis weights - // (1-f, 1, 1, f) this equals a 3x3 grid of hardware bilinear PCF taps. - vec2 size = vec2(textureSize(shadowmap, 0).xy); - vec2 uv = coords.xy * size - 0.5; - vec2 base = floor(uv); - vec2 f = uv - base; - float refz = coords.z + bias; - float layer = float(cascade); - // sampling at texel corners selects blocks with lower-left texel base-1 and base+1 - vec4 g00 = textureGather(shadowmap, vec3((base + vec2(0.0, 0.0)) / size, layer), refz); - vec4 g10 = textureGather(shadowmap, vec3((base + vec2(2.0, 0.0)) / size, layer), refz); - vec4 g01 = textureGather(shadowmap, vec3((base + vec2(0.0, 2.0)) / size, layer), refz); - vec4 g11 = textureGather(shadowmap, vec3((base + vec2(2.0, 2.0)) / size, layer), refz); - // gather component order: x=(0,1) y=(1,1) z=(1,0) w=(0,0) - float shadow = dot(g00, vec4(1.0 - f.x, 1.0, 1.0 - f.y, (1.0 - f.x) * (1.0 - f.y))) - + dot(g10, vec4(1.0, f.x, f.x * (1.0 - f.y), 1.0 - f.y)) - + dot(g01, vec4((1.0 - f.x) * f.y, f.y, 1.0, 1.0 - f.x)) - + dot(g11, vec4(f.y, f.x * f.y, f.x, 1.0)); - return shadow / 9.0; -#else - return 0.0; -#endif -} - -// ----------------------------------------------------------------------- -// GGX Microfacet BRDF helpers (Cook-Torrance) -// ----------------------------------------------------------------------- - -// Trowbridge-Reitz (GGX) Normal Distribution Function -// D(N,H,α) = α⁴ / (π · ((NdotH)²·(α⁴−1)+1)²) -// α = roughness² (perceptual remapping so the slider feels linear) -float D_GGX(float NdotH, float roughness) -{ - float a = roughness * roughness; // perceptual -> linear roughness - float a2 = a * a; - float d = (NdotH * NdotH) * (a2 - 1.0) + 1.0; - return a2 / (3.14159265359 * d * d); -} - -// Schlick-GGX single-term masking/shadowing (k remapped for direct lighting) -float G_SchlickGGX(float NdotX, float roughness) -{ - float r = roughness + 1.0; - float k = (r * r) * (1.0 / 8.0); // k_direct = (roughness+1)²/8 - return NdotX / (NdotX * (1.0 - k) + k); -} - -// Separable Smith geometry term (not height-correlated) -// G(N,V,L) = G_SchlickGGX(NdotV) · G_SchlickGGX(NdotL) -float G_Smith(float NdotV, float NdotL, float roughness) -{ - return G_SchlickGGX(NdotV, roughness) * G_SchlickGGX(NdotL, roughness); -} - -const float BRDF_PI = 3.14159265359; -// the smoothest surface the lights are reflected by: a mirror would make a highlight smaller than a pixel -const float MIN_ROUGHNESS = 0.045; -// cap of the brdf for the lamps and headlights (see specular_light()): a tiny light next to a polished surface -const float LOCAL_LIGHT_BRDF_LIMIT = 16.0; -// no cap for the sun -const float SUN_BRDF_LIMIT = 1.0e6; - -// the specular reflectance at normal incidence and the perceptual roughness the lights are reflected with, worked out by -// apply_lights() for the fragment before it adds the lamps (add_local_lights()) -vec3 spec_f0 = vec3(0.04); -float spec_roughness = 0.5; - -// a matte material (mat_default) reflects nothing: neither the surroundings nor the sun and the lamps, only the diffuse light -bool matte = false; - -// light of a light from Light_dir reflected by the surface, in the units of the diffuse shading (a white lambertian surface -// facing the light reflects 1 of it): pi times the cook-torrance brdf with schlick's fresnel of F0, times n.l. Limit caps the -// brdf without the n.l -vec3 specular_light(vec3 light_dir, vec3 fragnormal, vec3 f0, float roughness, float limit) -{ - float NdotL = dot(fragnormal, light_dir); - if (NdotL <= 0.0) - return vec3(0.0); - vec3 V = normalize(-f_pos.xyz); - vec3 H = normalize(light_dir + V); - float NdotV = max(dot(fragnormal, V), 1e-4); - float NdotH = max(dot(fragnormal, H), 0.0); - float VdotH = max(dot(V, H), 0.0); - float brdf = D_GGX(NdotH, roughness) * G_Smith(NdotV, NdotL, roughness) / (4.0 * NdotL * NdotV); - vec3 fresnel = f0 + (1.0 - f0) * pow(1.0 - VdotH, 5.0); - return fresnel * (min(brdf, limit) * NdotL * BRDF_PI); -} - -// perceptual roughness raised where the normal changes much from a pixel to the next (normal maps, tight curves), so the -// highlight of a detail smaller than a pixel spreads over the pixel instead of flickering. Kaplanyan, Tokuyoshi, "Improved -// geometric specular antialiasing", 2019 -float specular_antialiasing(vec3 fragnormal, float roughness) -{ - vec3 dndx = dFdx(fragnormal); - vec3 dndy = dFdy(fragnormal); - float variance = 0.25 * (dot(dndx, dndx) + dot(dndy, dndy)); - float kernel = min(2.0 * variance, 0.18); - float a2 = roughness * roughness * roughness * roughness; - return sqrt(sqrt(clamp(a2 + kernel, 0.0, 1.0))); -} - -// the materials without a specgloss map give glossiness: (param[1].w, 10 by default) as the exponent of the blinn-phong -// highlight of the old renderer. a lobe of exponent n is about a ggx one of alpha sqrt(2 / (n + 2)) -float phong_roughness(float exponent) -{ - return sqrt(sqrt(2.0 / (max(exponent, 0.0) + 2.0))); -} - -// glossiness which apply_lights() turns into the roughness of a blinn-phong exponent (it takes 1 - glossiness / param[1].w) -float phong_glossiness(float exponent) -{ - return (1.0 - phong_roughness(exponent)) * max(abs(param[1].w), 1.0); -} - -// the reflection of the surroundings of a material without a specgloss map is a smooth varnish over the rough surface: the -// old renderer showed it as a sharp reflection, as much of it as reflection: (and the masks of the material) asked for -bool legacy_coat = false; - -// set up the gloss of a material without a specgloss map, before apply_lights() -void legacy_gloss() -{ - glossiness = phong_glossiness(abs(param[1].w)); - legacy_coat = true; -} - -// Returns vec2(diffuse, specular) for a single punctual light. -// -// diffuse – Lambert N·L (Fresnel-weighted diffuse is handled per-material -// in apply_lights, so we return raw N·L here). -// specular – Cook-Torrance GGX: D·G / (4·NdotL·NdotV). -// The Fresnel factor (F) is intentionally omitted here; -// apply_lights already carries a per-material Fresnel term -// that is applied to env reflections and can be routed to -// direct specular there. -// -// Roughness is derived identically to env_roughness in apply_lights so -// that direct and indirect specular highlights read consistently. -vec2 calc_light(vec3 light_dir, vec3 fragnormal) -{ - vec3 N = fragnormal; - vec3 L = light_dir; - vec3 V = normalize(-f_pos.xyz); - vec3 H = normalize(L + V); - - float NdotL = max(dot(N, L), 0.0); - float NdotV = max(dot(N, V), 1e-4); - float NdotH = max(dot(N, H), 0.0); - - float diffuse_v = NdotL; - - // Mirror the env-map roughness derivation so direct and indirect lobes match. - // glossiness == param[1].w → roughness == 0.04 (near-mirror) - // glossiness == 0 → roughness == 1.0 (fully diffuse) - float roughness = clamp(1.0 - glossiness / max(abs(param[1].w), 1.0), 0.04, 1.0); - - // Cook-Torrance specular (no Fresnel — see above): - // f_spec = D(N,H,α) · G(N,V,L,α) / (4 · NdotL · NdotV) - float D = D_GGX(NdotH, roughness); - float G = G_Smith(NdotV, NdotL, roughness); - float specular_v = (NdotL > 0.0) - ? (D * G) / max(4.0 * NdotL * NdotV, 1e-4) - : 0.0; - - return vec2(diffuse_v, specular_v); -} - -vec2 calc_point_light(light_s light, vec3 fragnormal) -{ - vec3 light_dir = normalize(light.pos - f_pos.xyz); - vec2 val = calc_light(light_dir, fragnormal); - val.x += light.ambient; - val *= light.intensity; - - float distance = length(light.pos - f_pos.xyz); - float atten = 1.0f / (distance * distance); - //float atten = 1.0f / (1.0f + light.linear * distance + light.quadratic * (distance * distance)); - - return val * atten; -} - -vec2 calc_spot_light(light_s light, vec3 fragnormal) -{ - vec3 light_dir = normalize(light.pos - f_pos.xyz); - - float theta = dot(light_dir, normalize(-light.dir)); - float epsilon = light.in_cutoff - light.out_cutoff; - float intensity = clamp((theta - light.out_cutoff) / epsilon, 0.0, 1.0); - - vec2 point = calc_point_light(light, fragnormal); - return point * intensity; -} - -vec2 calc_dir_light(light_s light, vec3 fragnormal) -{ - vec3 light_dir = normalize(-light.dir); - return calc_light(light_dir, fragnormal); -} - -// samples of the soft shadows of the lamps and headlights: a disc, rotated per pixel -const vec2 SHADOW_DISC[8] = vec2[](vec2(-0.326, -0.406), vec2(-0.840, -0.074), vec2(-0.696, 0.457), vec2(-0.203, 0.621), - vec2(0.962, -0.195), vec2(0.473, -0.480), vec2(0.519, 0.767), vec2(0.185, -0.893)); - -float shadow_disc_angle() -{ - return 6.2831853 * fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715)))); -} - -// share of the beam of headlights with a shadow map (Map, headlight_weights.w - 1 of the light) held back before the fragment, -// 0..1. the map was drawn from the projector of the beam: x and y of the projection across it, z / w the depth (near plane over -// the distance along the axis, as the reverse z of the scene), w the distance. the comparison gives 1 where the map holds a -// value at least as large as the reference: a surface closer to the projector -float headlight_beam_shadow(uint map, vec3 fragnormal, vec3 light_dir) -{ - mat4 tomap = headlight_shadow[map]; - vec4 params = headlight_shadow_params[map]; - // a texel of the map is as large as this here. the point is moved along the normal by about a texel, and the reference towards - // the projector by a part of one. only the back faces are in the map, which leaves the lit faces little to shadow themselves with - float texel = params.x * max((tomap * vec4(f_pos.xyz, 1.0)).w, 0.0); - float NdotL = clamp(dot(fragnormal, light_dir), 0.0, 1.0); - vec4 position = tomap * vec4(f_pos.xyz + fragnormal * (texel * (0.5 + 1.5 * (1.0 - NdotL))), 1.0); - if (position.w <= 0.0) - return 0.0; - vec2 coords = position.xy / position.w; - if (any(lessThan(coords, vec2(0.0))) || any(greaterThan(coords, vec2(1.0)))) - return 0.0; - // nearer to the projector than its near plane (the vehicle itself) the reference is past anything in the map - float reference = position.z / max(position.w - texel * 1.5, 1e-3); - float layer = float(map); - float radius = params.y; - if (radius <= 0.0) - return texture(headlight_shadowmap, vec4(coords, layer, reference)); - float angle = shadow_disc_angle(); - vec2 rotation = vec2(cos(angle), sin(angle)); - float spread = radius / float(textureSize(headlight_shadowmap, 0).x); - int count = (radius > 1.5) ? 8 : 4; - float shadow = 0.0; - for (int i = 0; i < count; i++) - { - vec2 offset = vec2(SHADOW_DISC[i].x * rotation.x - SHADOW_DISC[i].y * rotation.y, SHADOW_DISC[i].x * rotation.y + SHADOW_DISC[i].y * rotation.x) * spread; - shadow += texture(headlight_shadowmap, vec4(coords + offset, layer, reference)); - } - return shadow / float(count); -} - -// dpdx and dpdy are the change of the view space position of the fragment from a pixel to the next one. the beam texture is -// sampled with gradients worked out from them instead of the ones of the hardware: those are undefined where the fragments of -// a quad do not go through the same lights, which is what happens at the edges of the clusters -vec2 calc_headlights(light_s light, vec3 fragnormal, vec3 dpdx, vec3 dpdy) -{ - vec4 headlightpos = light.headlight_projection * f_pos; - vec3 coords = headlightpos.xyz / headlightpos.w; - - if (coords.z > 1.0) - return vec2(0.0); - if (coords.z < 0.0) - return vec2(0.0); - // the beam is what the projection takes in. beyond its sides the texture would be clamped to its edge, and near the projector - // (where a pixel spans many texels) its coarsest levels spill the average of the whole beam over the surroundings - if (coords.x < 0.0 || coords.x > 1.0 || coords.y < 0.0 || coords.y > 1.0) - return vec2(0.0); - - vec3 light_dir = normalize(light.pos - f_pos.xyz); - // Tighter wrap (was +0.25): faces angled away from the headlight cone - // fall off to dark much faster, so cab/exterior surfaces read with a - // clear directional shape instead of a flat half-lit wash. - vec2 part = vec2(1.0) * clamp(dot(fragnormal, light_dir) + 0.10, 0.0, 1.0); - float distance = length(light.pos - f_pos.xyz); - float atten = 1.0f / (1.0f + light.linear * distance + light.quadratic * (distance * distance)); - atten *= mix(1.0, 0.0, clamp((coords.z - 0.998) * 500.0, 0.0, 1.0)); - vec2 beamcoords = headlightpos.xy / headlightpos.w; - vec4 dheadlightposdx = light.headlight_projection * vec4(dpdx, 0.0); - vec4 dheadlightposdy = light.headlight_projection * vec4(dpdy, 0.0); - vec2 dbeamdx = (dheadlightposdx.xy - beamcoords * dheadlightposdx.w) / headlightpos.w; - vec2 dbeamdy = (dheadlightposdy.xy - beamcoords * dheadlightposdy.w) / headlightpos.w; - vec3 lights = textureGrad(headlightmap, beamcoords, dbeamdx, dbeamdy).rgb * light.headlight_weights.rgb; - float lightintensity = max(max(lights.r, lights.g), lights.b); - // headlights with a shadow map: what is in the way holds the beam back - if ((light.headlight_weights.w > 0.5) && (lightintensity > 0.0) && (part.x > 0.0)) - lightintensity *= 1.0 - headlight_beam_shadow(uint(light.headlight_weights.w - 0.5), fragnormal, light_dir); - if ((contact_shadow_budget > 0) && (lightintensity > 0.0) && (part.x > 0.0)) - lightintensity *= 1.0 - contact_shadow_lamp(light_dir, distance, - part.x * atten * lightintensity * light.intensity * max(light.color.r, max(light.color.g, light.color.b))); - // x the diffuse light, y the light the specular reflection takes (specular_light() brings its own n.l) - return vec2(part.x, 1.0) * (atten * lightintensity); -} - -// closest distance (squared) the light of a lamp is computed for: nearer the inverse square would grow without a limit -const float LOCAL_LIGHT_MIN_DISTANCE2 = 0.01; - -// one comparison with the cube shadow map at the point of the space of the map, moved towards the lamp by Bias (metres). the -// map is in the array of the scenery lamps when Lamps, in the one of the cab lamp otherwise -float local_light_shadow_tap(vec3 position, float layer, float near, float bias, bool lamps) -{ - vec3 axes = abs(position); - float major = max(axes.x, max(axes.y, axes.z)); - float reference = near / max(major - bias, near); - return lamps ? texture(local_shadowmap_lamps, vec4(position, layer), reference) : texture(local_shadowmap, vec4(position, layer), reference); -} - -// share of the light of a lamp with a cube shadow map (headlight_weights.x is its layer) which reaches the fragment, 0..1. -// the map holds n / d of the closest surface, d the distance along the axis of the face, n the near plane: the farther, the -// smaller (as the depth of the scene, reverse z). the comparison gives 1 where the map holds a value at least as large as -// the reference, i.e. a surface at most as far: in shadow -float local_light_shadow(light_s light, vec3 fragnormal) -{ - // the space of the map: origin at the lamp, axes of the model the shadows are cast by - vec3 position = (light.headlight_projection * vec4(f_pos.xyz, 1.0)).xyz; - vec3 normal = normalize(mat3(light.headlight_projection) * fragnormal); - float distance = length(position); - // a texel of the map is as large as this here. the point is moved along the normal by about a texel, which keeps a surface - // from shadowing itself at grazing angles, and the reference is moved towards the lamp by a part of a texel - float texel = light.headlight_weights.z * distance; - vec3 tolamp = -position / max(distance, 1e-4); - float NdotL = clamp(dot(normal, tolamp), 0.0, 1.0); - position += normal * (texel * (0.5 + 1.5 * (1.0 - NdotL))); - float bias = texel * 1.5; - float layer = light.headlight_weights.x; - float radius = light.headlight_weights.w; - bool lamps = (light.linear > 0.5); - if (radius <= 0.0) - return local_light_shadow_tap(position, layer, light.headlight_weights.y, bias, lamps); - // a disc of samples in the plane of the surface, each compared with its own distance: points of the surface itself, which - // samples spread across the direction to the lamp would not be at grazing angles. rotated per pixel to turn the banding into noise - vec3 side = normalize(cross(normal, (abs(normal.y) < 0.99) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0))); - vec3 up = cross(normal, side); - float angle = shadow_disc_angle(); - vec2 rotation = vec2(cos(angle), sin(angle)); - float spread = texel * radius; - int count = (radius > 1.5) ? 8 : 4; - float shadow = 0.0; - for (int i = 0; i < count; i++) - { - vec2 offset = vec2(SHADOW_DISC[i].x * rotation.x - SHADOW_DISC[i].y * rotation.y, SHADOW_DISC[i].x * rotation.y + SHADOW_DISC[i].y * rotation.x) * spread; - shadow += local_light_shadow_tap(position + side * offset.x + up * offset.y, layer, light.headlight_weights.y, bias, lamps); - } - return shadow / float(count); -} - -// light of a lamp (point or spot light: the lamps of models and vehicles, the cab lamp) reaching the fragment, x the diffuse -// part and y the light the specular reflection takes (specular_light() brings its own n.l). it falls off with the square of the distance, and smoothly to nothing at the range of the light, -// 1 / sqrt(quadratic), past which the culling of the clusters does not take it. a spot light fades from its inner cone to the -// outer one. a lamp with a shadow map is held back by what is between it and the fragment -vec2 calc_local_light(light_s light, vec3 fragnormal) -{ - vec3 tolight = light.pos - f_pos.xyz; - float distance2 = dot(tolight, tolight); - vec3 light_dir = tolight * inversesqrt(max(distance2, 1e-8)); - float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0); - float atten = edge * edge / max(distance2, LOCAL_LIGHT_MIN_DISTANCE2); - if (light.type == LIGHT_SPOT) - atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-light_dir, normalize(light.dir))); - if ((atten > 0.0) && (light.headlight_weights.x >= 0.0) && (dot(fragnormal, light_dir) > 0.0)) - atten *= 1.0 - local_light_shadow(light, fragnormal); - if ((atten > 0.0) && (contact_shadow_budget > 0)) - atten *= 1.0 - contact_shadow_lamp(light_dir, sqrt(distance2), - atten * max(dot(fragnormal, light_dir), 0.0) * light.intensity * max(light.color.r, max(light.color.g, light.color.b))); - if (atten <= 0.0) - return vec2(0.0); - return vec2(max(dot(fragnormal, light_dir), 0.0), 1.0) * atten; -} - -// index of the light cluster the fragment is in, see gl/cluster_grid.h: its screen tile and slice of depth. the depth is the clip -// space w, which is 1 / gl_FragCoord.w. a tile is kept in the grid, as the far edge of the render target and rounding -// right below it give one past the last -uint fragment_cluster() -{ - ivec2 tile = ivec2(gl_FragCoord.xy * (vec2(CLUSTER_GRID_X, CLUSTER_GRID_Y) / cluster_viewport_size)); - tile = clamp(tile, ivec2(0), ivec2(CLUSTER_GRID_X - 1, CLUSTER_GRID_Y - 1)); - int slice = clamp(int(floor(cluster_slice_bias - log(gl_FragCoord.w) * cluster_slice_scale)), 0, CLUSTER_GRID_Z - 1); - return uint(tile.x + CLUSTER_GRID_X * (tile.y + CLUSTER_GRID_Y * slice)); -} - -// adds the light of a headlight or lamp to the diffuse and the specular light of the fragment -void add_local_light(light_s light, vec2 part, vec3 fragnormal, inout vec3 diffuse, inout vec3 specular) -{ - if (part.y <= 0.0) - return; - vec3 color = light.color * light.intensity; - diffuse += color * part.x; - if (matte) - return; - specular += color * (part.y * param[1].y) * specular_light(normalize(light.pos - f_pos.xyz), fragnormal, spec_f0, spec_roughness, LOCAL_LIGHT_BRDF_LIMIT); -} - -// adds the light of the headlights and lamps which reach the fragment to its diffuse and specular light. these are the ones in -// the list of its cluster when the lights are clustered (the sun is not on the list, the callers take care of it), and the -// first lights_count - 1 of lights[] otherwise, which are headlights only. the specular light is reflected with spec_f0 and -// spec_roughness, and is already coloured by the fresnel term -void add_local_lights(vec3 fragnormal, inout vec3 diffuse, inout vec3 specular) -{ - // worked out before the loops, which the fragments of a quad do not leave together - vec3 dpdx = dFdx(f_pos.xyz); - vec3 dpdy = dFdy(f_pos.xyz); - contact_shadow_budget = (contact_shadow_params.x > 0.0) ? int(contact_shadow_lamps.x) : 0; - - if (lights_clustered != 0U) - { - cluster_range_s range = cluster_ranges[fragment_cluster()]; - for (uint i = 0U; i < range.count; i++) - { - light_s light = lights[cluster_light_indices[range.offset + i]]; - vec2 part = (light.type == LIGHT_HEADLIGHTS) ? calc_headlights(light, fragnormal, dpdx, dpdy) : calc_local_light(light, fragnormal); - add_local_light(light, part, fragnormal, diffuse, specular); - } - } - else - { - for (uint i = 1U; i < lights_count; i++) - { - light_s light = lights[i]; - add_local_light(light, calc_headlights(light, fragnormal, dpdx, dpdy), fragnormal, diffuse, specular); - } - } -} - -// the light of the headlights and lamps added to Fragcolor, diffuse and specular together (for shaders which don't tell -// the albedo from the reflection) -vec3 add_headlights(vec3 fragcolor, vec3 fragnormal) -{ - vec3 diffuse = vec3(0.0); - vec3 specular = vec3(0.0); - add_local_lights(fragnormal, diffuse, specular); - return fragcolor + diffuse + specular; -} - -// ----------------------------------------------------------------------- -// Split-sum environment BRDF (Karis / UE4 analytic approximation). -// -// This is the missing piece that made matte specgloss surfaces "shine like -// crazy": previously the env reflection was added at full strength -// (envcolor * fresnel * reflectivity) and roughness only blurred the mip, -// never dimmed the energy. A rough surface therefore mirrored the bright -// sky just as strongly as a polished one. -// -// EnvBRDFApprox returns the pre-integrated specular scale (the "DFG" term) -// for a given F0, roughness and view angle. For rough surfaces it collapses -// toward ~0, so low-glossiness materials reflect almost nothing — matching -// what Substance 3D Painter shows. Polished surfaces keep their full -// reflection, and the grazing-angle Fresnel edge is preserved. -// roughness 1.0 (matte) -> scale ~0.015 (virtually no reflection) -// roughness 0.0 (mirror) -> scale ~F0..1 (full reflection + Fresnel rim) -vec3 EnvBRDFApprox(vec3 F0, float roughness, float NoV) -{ - const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022); - const vec4 c1 = vec4( 1.0, 0.0425, 1.040, -0.040); - vec4 r = roughness * c0 + c1; - float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y; - vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw; - return F0 * AB.x + AB.y; -} - -// Diffuse colour of the submodel (t3d/e3d) or shape, used as the albedo multiplier: -// untextured materials take it as their colour, textured ones multiply the base texture by it -// (same as the legacy renderer's GL_MODULATE). param[0].rgb holds the authored sRGB colour, -// param[1].x its intensity (HSV value unless a material overrides it). -vec3 material_diffuse() -{ - vec3 diffusecolor = pow(max(param[0].rgb, vec3(0.0)), vec3(2.2)); - float value = max(param[0].r, max(param[0].g, param[0].b)); - return (value > 0.0 ? diffusecolor * (param[1].x / value) : vec3(0.0)); -} - -// directional part of the sky ambient light (sky_ssbo, bands 1 and 2 of the spherical harmonics) for a world space direction -vec3 sky_ambient_directional(vec3 n) -{ - return sky_ambient_sh[1].rgb * (0.488603 * n.y) - + sky_ambient_sh[2].rgb * (0.488603 * n.z) - + sky_ambient_sh[3].rgb * (0.488603 * n.x) - + sky_ambient_sh[4].rgb * (1.092548 * n.x * n.y) - + sky_ambient_sh[5].rgb * (1.092548 * n.y * n.z) - + sky_ambient_sh[6].rgb * (0.315392 * (3.0 * n.z * n.z - 1.0)) - + sky_ambient_sh[7].rgb * (1.092548 * n.x * n.z) - + sky_ambient_sh[8].rgb * (0.546274 * (n.x * n.x - n.y * n.y)); -} - -// adds the directional part of the sky light to the ambient light the material starts from (the uniform ambient, or a share -// of it, like the water takes). the same share of the directional part is added, so with the directional part at zero -// (gfx.ambient.sh off) the result is what it always was -vec3 add_sky_ambient(vec3 basecolor, vec3 fragnormal) -{ - if (sky_ambient_factor == 0.0) - return basecolor; - float share = dot(basecolor, vec3(1.0)) / max(dot(ambient, vec3(1.0)), 1e-4); - vec3 worldnormal = normalize(mat3(inv_view) * fragnormal); - return max(basecolor + sky_ambient_directional(worldnormal) * (sky_ambient_factor * share), vec3(0.0)); -} - -// lights the surface: the ambient light, the sun (the moon), the headlights and lamps and the reflection of the surroundings. -// the units are the ones of the ambient light, the irradiance over pi: a white lambertian surface lit by 1 of it shows 1. -// Texturecolor is the albedo (the base colour of a metal), Specularity the specular level of a dielectric (0.5 is the -// reflectance of 4% at normal incidence), Reflectivity how much of the reflected surroundings is the cubemap rather than the -// ambient light, Shadowtone the share of the sun left in its shadow (1 for the materials which take no shadows). -// a Sunless surface (the interior of a cab) gets no light of the sun, its shadow only darkens the ambient light -vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone, bool sunless) -{ - // Diffuse colour is applied once, to the albedo; the light terms below are not scaled by it again - texturecolor *= material_diffuse(); - float polished = 0.0; - if (sunless) - { - // Cab interior: dim ambient less than the exterior path - ambient - // is the dominant indoor illumination, but it was still too bright. - fragcolor *= 0.80; - } - else - { - // the sky lights the faces turned up and towards the bright part of the sky more than the ones turned to the ground - fragcolor = add_sky_ambient(fragcolor, fragnormal); -#ifndef NO_WEATHERING - float weathered = (param[2].w > 0.0 ? clamp(param[2].w * UserData.x, 0.0, 1.0) : 0.0); - float tinted = (param[2].w < 0.0 ? clamp(-param[2].w * UserData.x, 0.0, 1.0) : 0.0); - // a tint only scales the colour of the texture, the steel keeps its own polish - polished = weathered * smoothstep(0.03, 0.12, dot(texturecolor, vec3(0.2126, 0.7152, 0.0722))); - float weatheredluma = min(dot(texturecolor, vec3(0.2126, 0.7152, 0.0722)), 0.05); - vec3 unused = texturecolor * (weatheredluma / max(dot(texturecolor, vec3(0.2126, 0.7152, 0.0722)), 1e-4)); - unused = mix(unused, vec3(1.0, 0.82, 0.74) * (weatheredluma / 0.8525), 0.5); - texturecolor = mix(texturecolor, unused, smoothstep(0.0, 0.4, weathered)); - texturecolor = (tinted > 0.0 ? texturecolor * mix(vec3(1.0), param[2].rgb, tinted) : texturecolor); - specularity = mix(specularity, min(specularity, 0.03), smoothstep(0.0, 0.4, polished)); - metalic = mix(metalic, 0.0, polished); - glossiness = mix(glossiness, min(glossiness, 0.06 * abs(param[1].w)), polished); - reflectivity = mix(reflectivity, min(reflectivity, 0.08 * param[1].z), smoothstep(0.0, 0.4, polished)); -#endif - // Scale ambient before it gets tinted by the texture. - // Sun, headlights and emission are added afterwards so they are NOT - // attenuated by AMBIENT_SCALE - this only dims the indirect term. - fragcolor *= AMBIENT_SCALE; - } - - vec3 emissioncolor = vec3(emission); - vec3 view_dir = normalize(-f_pos.xyz); - float NdotV = max(dot(fragnormal, view_dir), 1e-4); - - // glossiness == param[1].w -> mirror, glossiness == 0 -> fully rough - float roughness = clamp(1.0 - glossiness / max(abs(param[1].w), 1.0), 0.0, 1.0); - roughness = max(specular_antialiasing(fragnormal, roughness), MIN_ROUGHNESS); - // metals reflect with their base colour, dielectrics with the specular level (0.5 -> 0.04) - vec3 f0 = mix(vec3(0.08 * specularity), texturecolor, metalic); - spec_f0 = f0; - spec_roughness = roughness; - - // Pre-integrated env BRDF: how much of the surroundings the surface reflects, for its F0, roughness and the view angle. - // the surroundings are the cubemap where the material asks for it (reflectivity) and the ambient light elsewhere, so a - // surface reflects them always, the rough one barely - vec3 env_spec = EnvBRDFApprox(f0, roughness, NdotV); - float env_roughness = roughness; - vec3 cubemap_spec = env_spec; - float cubemap_share = max(reflectivity, 0.0); - float ambient_share = clamp(1.0 - reflectivity, 0.0, 1.0); - if (matte) - { - env_spec = vec3(0.0); - cubemap_share = 0.0; - } - // what the diffuse layer is left with - vec3 diffuse_albedo = texturecolor * (1.0 - metalic) * (1.0 - env_spec); - if (legacy_coat) - { - // the varnish: a smooth dielectric layer reflecting the cubemap, worn off where the material is weathered - env_roughness = specular_antialiasing(fragnormal, roughness * smoothstep(0.0, 0.4, polished)); - cubemap_spec = EnvBRDFApprox(mix(vec3(0.04), texturecolor, metalic), env_roughness, NdotV); - ambient_share = 1.0; - diffuse_albedo *= 1.0 - cubemap_spec * min(cubemap_share, 1.0); - } - vec3 envcolor = (matte ? vec3(0.0) : envmap_color_rough(fragnormal, env_roughness)); - - if (lights_count == 0U) - return fragcolor * diffuse_albedo - + emissioncolor * texturecolor - + fragcolor * env_spec * ambient_share - + envcolor * cubemap_spec * cubemap_share; - - vec3 light_dir = normalize(-lights[0].dir); - float NdotL = max(dot(fragnormal, light_dir), 0.0); - // Sharpen sun N.L falloff so the lit-to-shaded terminator on cab - // panels, vehicle bodies and terrain reads as a clear edge rather - // than a soft Lambertian ramp. Tunable via SUN_NDOTL_SHARPNESS. - float sun_NdotL = pow(NdotL, SUN_NDOTL_SHARPNESS); - // the clouds hold back the sun (the moon at night) where their shadow falls - float cloudsun = cloud_shadow(mat3(inv_view) * f_pos.xyz); - // Sun HDR scale -> SUN_DIFFUSE_SCALE, the same for the diffuse and the specular light - vec3 sunlight = lights[0].color * (SUN_DIFFUSE_SCALE * lights[0].intensity * cloudsun); - - float shadow = 0.0; - if (shadowtone < 1.0) - shadow = clamp(calc_shadow(), 0.0, 1.0); - - // the cubemap knows nothing of what shades the fragment: it is dimmed a little in the shadow of the sun - float cubemap_shadow = 1.0; - if (sunless) - { - // the sun adds no light in the cab, where it would fall its shadow darkens the ambient light - float diffuseamount = sun_NdotL * lights[0].intensity; - float darkness = (shadowtone < 1.0 ? max(shadow, 1.0 - cloudsun) : 0.0); - fragcolor = mix(fragcolor, fragcolor * shadowtone, clamp(diffuseamount * darkness, 0.0, 1.0)); - } - else - cubemap_shadow = 1.0 - (1.0 - shadowtone) * shadow * 0.5; - - // the reflection of the surroundings, before the light of the sun and the lamps joins the ambient light - vec3 env_reflection = fragcolor * env_spec * ambient_share - + envcolor * cubemap_spec * (cubemap_share * cubemap_shadow); - - // the shadow leaves the share of the sun Shadowtone tells (the old shadow colour of the scenery) to the diffuse light, - // nothing to the highlight - if (!sunless) - fragcolor += sunlight * (sun_NdotL * (1.0 - (1.0 - shadowtone) * shadow)); - vec3 specular = matte ? vec3(0.0) : sunlight * specular_light(light_dir, fragnormal, f0, roughness, SUN_BRDF_LIMIT) * (1.0 - shadow); - - vec3 lampspecular = vec3(0.0); - add_local_lights(fragnormal, fragcolor, lampspecular); - - return fragcolor * diffuse_albedo // ambient, sun and lamps on the diffuse layer - + specular * param[1].y // highlight of the sun, fresnel coloured (metals take their base colour) - + lampspecular // highlights of the lamps, param[1].y already applied - + emissioncolor * texturecolor - + env_reflection; -} - -vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone) -{ - return shade_surface(fragcolor, fragnormal, texturecolor, reflectivity, specularity, shadowtone, false); -} - -// the interior of a cab: no light of the sun, its shadow darkens the ambient light -vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone) -{ - return shade_surface(fragcolor, fragnormal, texturecolor, reflectivity, specularity, shadowtone, true); -} +layout(binding = SHADOW_TEX) uniform sampler2DArrayShadow shadowmap; +layout(binding = HEADLIGHT_TEX) uniform sampler2D headlightmap; +// cube shadow maps of the lamps, see gl::LOCAL_SHADOW_TEX and opengl46_local_shadows +layout(binding = LOCAL_SHADOW_TEX) uniform samplerCubeArrayShadow local_shadowmap; +// the same for the lamps of scenery models, the lights with linear 1 +layout(binding = LOCAL_SHADOW_LAMPS_TEX) uniform samplerCubeArrayShadow local_shadowmap_lamps; +// shadow maps of the headlights of vehicles, see gl::HEADLIGHT_SHADOW_TEX and gl::scene_ubs::headlight_shadow +layout(binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_shadowmap; +// depth of the opaque scene drawn before the colour, for the contact shadows (gl::scene_ubs::contact_shadow_params) +layout(binding = SCENE_DEPTH_TEX) uniform sampler2D scene_depth; + +#include "envmapping.glsl" +#include "conversion.glsl" +#include "weather_common.glsl" + +float glossiness = 1.0; +float metalic = 0.0; +layout(location = 7) in vec4 UserData; +#pragma param(weathering, 2, 3, 1, zero) + +// --------------------------------------------------------------------- +// Lighting balance tunables - tweak these to control overall scene +// exposure without touching tonemapping.glsl. +// +// AMBIENT_SCALE: brightness of SHADED faces (indirect/sky term). +// Lower -> deeper shadows, less burn under bright +// textures. Higher -> flatter / brighter shading. +// +// SUN_DIFFUSE_SCALE: brightness of UNSHADED (sun-lit) faces. Lower +// this to dim hot surfaces in direct sunlight +// without affecting shaded areas. Was 3.5; 2.5 +// calmly fits the ACES tonemap shoulder. +// +// SUN_NDOTL_SHARPNESS: N.L curve on the sun. 1.0 = pure Lambert, higher +// = sharper terminator (more contrast between +// lit and shaded faces of the same surface). +// --------------------------------------------------------------------- +const float AMBIENT_SCALE = 0.65; +const float SUN_DIFFUSE_SCALE = 1.5; +const float SUN_NDOTL_SHARPNESS = 1.25; + +float length2(vec3 v) +{ + return dot(v, v); +} + +// how many more lamps and headlights the fragment traces contact shadows to, see add_headlights() +int contact_shadow_budget = 0; + +// contact shadow of a light in direction Light_dir (view space, towards the light), 0..1: whether the depth of the scene drawn +// before the colour (scene_depth) holds a surface between the fragment and a point up to Length metres towards the light. it +// catches what the shadow maps are too coarse for (rails on the sleepers, bolts, edges of panels, where a thing stands on the +// ground), and things too small or near for them. only what is in the depth casts it, and only within the image. +// the ray starts a pixel and a half off the visible side of the surface: the depth there, sampled anywhere within a pixel of +// the projected point, then stays behind the ray on a flat surface, so it doesn't shadow itself. a surface nearer than the +// ray by more than the thickness is taken for something in front of the ray, not something it goes through. +// the shadow fades over the last part of the ray (it doesn't end with an edge) and over the last quarter of the range +float contact_shadow(vec3 light_dir, float raylength, float steps) +{ + float range = contact_shadow_params.y; + vec3 position = f_pos.xyz; + float fade = 1.0 - smoothstep(range * 0.75, range, length(position)); + if ((fade <= 0.0) || (raylength <= 0.0) || (steps < 1.0)) + return 0.0; + vec3 normal = normalize(f_normal); + if (dot(normal, position) > 0.0) + normal = -normal; + if (dot(normal, light_dir) <= 0.0) + return 0.0; + vec2 size = vec2(textureSize(scene_depth, 0)); + // the projection is perspective with the depth n / w (reverse z, infinite far plane): w is the distance along the axis + // of the view, and a depth d is a surface at n / d + float near = projection[3][2]; + float distance = (projection * vec4(position, 1.0)).w; + float pixel = 2.0 * distance / (abs(projection[1][1]) * size.y); + vec4 start = projection * vec4(position + normal * (pixel * 1.5), 1.0); + vec4 ray = projection * vec4(light_dir * raylength, 0.0); + float thickness = 0.3 + pixel * 4.0; + float bias = pixel * 0.5; + // a different start along the ray in neighbouring pixels: the steps give noise instead of bands + float jitter = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715)))); + for (float i = 0.0; i < steps; i += 1.0) + { + float t = (i + jitter) / steps; + vec4 clip = start + ray * t; + if (clip.w <= near) + break; + vec2 coords = (clip.xy / clip.w) * 0.5 + 0.5; + if (any(lessThan(coords, vec2(0.0))) || any(greaterThan(coords, vec2(1.0)))) + break; + float depth = texelFetch(scene_depth, min(ivec2(coords * size), ivec2(size) - 1), 0).r; + // nothing drawn there (the sky) + if (depth <= 0.0) + continue; + float nearer = clip.w - near / depth; + if ((nearer > bias) && (nearer < thickness)) + return fade * (1.0 - smoothstep(0.6, 1.0, t)); + } + return 0.0; +} + +// contact shadow of a lamp or headlight Distance metres away, whose light reaches the fragment with Strength, for the first +// few lamps of a fragment (contact_shadow_lamps.x) which light it noticeably. the ray goes at most half way to the light, +// so the lamp doesn't shadow with its own fitting +float contact_shadow_lamp(vec3 light_dir, float distance, float strength) +{ + if ((contact_shadow_budget <= 0) || (strength < 0.005)) + return 0.0; + --contact_shadow_budget; + return contact_shadow(light_dir, min(contact_shadow_params.x, distance * 0.5), contact_shadow_params.w); +} + +// share of the sun (the moon) held back before the fragment by the shadow map and the contact shadow, 0..1 +float calc_shadow_map(); +float calc_shadow() +{ + float shadow = calc_shadow_map(); + if ((contact_shadow_params.x > 0.0) && (shadow < 1.0)) + shadow = max(shadow, contact_shadow(normalize(-lights[0].dir), contact_shadow_params.x, contact_shadow_params.z)); + return shadow; +} + +float calc_shadow_map() +{ + if (SHADOWMAP_ENABLED) + { + float distance = dot(f_pos.xyz, f_pos.xyz); + uint cascade; + for (cascade = 0U; cascade < MAX_CASCADES; cascade++) + if (distance <= cascade_end[cascade]) + break; + // cascades fitted to the depth of the view of an earlier frame (gfx.shadow.sdsm) cover only the area of the points seen + // then: a point at the edge of the view or one which came closer since may be outside the map of its cascade. it goes to + // the first further cascade whose map holds it with the reach of the filter, and is lit past the last one + if (shadow_fit.x > 0.0) + { + float border = 4.0 / float(textureSize(shadowmap, 0).x); + for (; cascade < MAX_CASCADES; cascade++) + { + vec2 fitcoords = (lightview[cascade] * vec4(f_pos.xyz, 1.0)).xy; + if (all(greaterThanEqual(fitcoords, vec2(border))) && all(lessThanEqual(fitcoords, vec2(1.0 - border)))) + break; + } + if (cascade >= MAX_CASCADES) + return 0.0; + } + // Normal offset: sample the shadow map from a point pushed along the surface normal + // by a fraction of the cascade's texel footprint. A constant depth bias can't keep up + // with large texels (far cascades, small shadow maps) on surfaces at a grazing angle + // to the sun, which then show shadow acne stripes. lightview maps view space metres + // to [0,1] shadow map coordinates, so its x/y row lengths give texels per metre. + vec3 shadownormal = normalize(f_normal); + float shadowNdotL = clamp(dot(shadownormal, normalize(-lights[0].dir)), 0.0, 1.0); + mat4 shadowview = lightview[cascade]; + float texelspermetre = min(length(vec3(shadowview[0][0], shadowview[1][0], shadowview[2][0])), + length(vec3(shadowview[0][1], shadowview[1][1], shadowview[2][1]))) * float(textureSize(shadowmap, 0).x); + float texelsize = 1.0 / texelspermetre; + vec3 shadowpos = f_pos.xyz + shadownormal * (texelsize * 1.5 * (1.0 - shadowNdotL)); + vec4 lightpos = shadowview * vec4(shadowpos, 1.0); + vec3 coords = lightpos.xyz / lightpos.w; + if (coords.z < 0.0) + return 0.0f; + + + + float bias = 0.00005f * float(cascade + 1U); + + // Fast path: 4 textureGather() calls cover a 4x4 texel block. Raw gathered + // comparisons must be weighted by the sub-texel position (bilinear weights), + // otherwise the result is constant across each shadow map texel and shadow + // edges turn into texel-sized stair steps. With per-axis weights + // (1-f, 1, 1, f) this equals a 3x3 grid of hardware bilinear PCF taps. + vec2 size = vec2(textureSize(shadowmap, 0).xy); + vec2 uv = coords.xy * size - 0.5; + vec2 base = floor(uv); + vec2 f = uv - base; + float refz = coords.z + bias; + float layer = float(cascade); + // sampling at texel corners selects blocks with lower-left texel base-1 and base+1 + vec4 g00 = textureGather(shadowmap, vec3((base + vec2(0.0, 0.0)) / size, layer), refz); + vec4 g10 = textureGather(shadowmap, vec3((base + vec2(2.0, 0.0)) / size, layer), refz); + vec4 g01 = textureGather(shadowmap, vec3((base + vec2(0.0, 2.0)) / size, layer), refz); + vec4 g11 = textureGather(shadowmap, vec3((base + vec2(2.0, 2.0)) / size, layer), refz); + // gather component order: x=(0,1) y=(1,1) z=(1,0) w=(0,0) + float shadow = dot(g00, vec4(1.0 - f.x, 1.0, 1.0 - f.y, (1.0 - f.x) * (1.0 - f.y))) + + dot(g10, vec4(1.0, f.x, f.x * (1.0 - f.y), 1.0 - f.y)) + + dot(g01, vec4((1.0 - f.x) * f.y, f.y, 1.0, 1.0 - f.x)) + + dot(g11, vec4(f.y, f.x * f.y, f.x, 1.0)); + return shadow / 9.0; + } + else + { + return 0.0; + } +} + +// ----------------------------------------------------------------------- +// GGX Microfacet BRDF helpers (Cook-Torrance) +// ----------------------------------------------------------------------- + +// Trowbridge-Reitz (GGX) Normal Distribution Function +// D(N,H,α) = α⁴ / (π · ((NdotH)²·(α⁴−1)+1)²) +// α = roughness² (perceptual remapping so the slider feels linear) +float D_GGX(float NdotH, float roughness) +{ + float a = roughness * roughness; // perceptual -> linear roughness + float a2 = a * a; + float d = (NdotH * NdotH) * (a2 - 1.0) + 1.0; + return a2 / (3.14159265359 * d * d); +} + +// Schlick-GGX single-term masking/shadowing (k remapped for direct lighting) +float G_SchlickGGX(float NdotX, float roughness) +{ + float r = roughness + 1.0; + float k = (r * r) * (1.0 / 8.0); // k_direct = (roughness+1)²/8 + return NdotX / (NdotX * (1.0 - k) + k); +} + +// Separable Smith geometry term (not height-correlated) +// G(N,V,L) = G_SchlickGGX(NdotV) · G_SchlickGGX(NdotL) +float G_Smith(float NdotV, float NdotL, float roughness) +{ + return G_SchlickGGX(NdotV, roughness) * G_SchlickGGX(NdotL, roughness); +} + +const float BRDF_PI = 3.14159265359; +// the smoothest surface the lights are reflected by: a mirror would make a highlight smaller than a pixel +const float MIN_ROUGHNESS = 0.045; +// cap of the brdf for the lamps and headlights (see specular_light()): a tiny light next to a polished surface +const float LOCAL_LIGHT_BRDF_LIMIT = 16.0; +// no cap for the sun +const float SUN_BRDF_LIMIT = 1.0e6; + +// the specular reflectance at normal incidence and the perceptual roughness the lights are reflected with, worked out by +// apply_lights() for the fragment before it adds the lamps (add_local_lights()) +vec3 spec_f0 = vec3(0.04); +float spec_roughness = 0.5; + +// a matte material (mat_default) reflects nothing: neither the surroundings nor the sun and the lamps, only the diffuse light +bool matte = false; + +// light of a light from Light_dir reflected by the surface, in the units of the diffuse shading (a white lambertian surface +// facing the light reflects 1 of it): pi times the cook-torrance brdf with schlick's fresnel of F0, times n.l. Limit caps the +// brdf without the n.l +vec3 specular_light(vec3 light_dir, vec3 fragnormal, vec3 f0, float roughness, float limit) +{ + float NdotL = dot(fragnormal, light_dir); + if (NdotL <= 0.0) + return vec3(0.0); + vec3 V = normalize(-f_pos.xyz); + vec3 H = normalize(light_dir + V); + float NdotV = max(dot(fragnormal, V), 1e-4); + float NdotH = max(dot(fragnormal, H), 0.0); + float VdotH = max(dot(V, H), 0.0); + float brdf = D_GGX(NdotH, roughness) * G_Smith(NdotV, NdotL, roughness) / (4.0 * NdotL * NdotV); + vec3 fresnel = f0 + (1.0 - f0) * pow(1.0 - VdotH, 5.0); + return fresnel * (min(brdf, limit) * NdotL * BRDF_PI); +} + +// perceptual roughness raised where the normal changes much from a pixel to the next (normal maps, tight curves), so the +// highlight of a detail smaller than a pixel spreads over the pixel instead of flickering. Kaplanyan, Tokuyoshi, "Improved +// geometric specular antialiasing", 2019 +float specular_antialiasing(vec3 fragnormal, float roughness) +{ + vec3 dndx = dFdx(fragnormal); + vec3 dndy = dFdy(fragnormal); + float variance = 0.25 * (dot(dndx, dndx) + dot(dndy, dndy)); + float kernel = min(2.0 * variance, 0.18); + float a2 = roughness * roughness * roughness * roughness; + return sqrt(sqrt(clamp(a2 + kernel, 0.0, 1.0))); +} + +// the materials without a specgloss map give glossiness: (param[1].w, 10 by default) as the exponent of the blinn-phong +// highlight of the old renderer. a lobe of exponent n is about a ggx one of alpha sqrt(2 / (n + 2)) +float phong_roughness(float exponent) +{ + return sqrt(sqrt(2.0 / (max(exponent, 0.0) + 2.0))); +} + +// glossiness which apply_lights() turns into the roughness of a blinn-phong exponent (it takes 1 - glossiness / param[1].w) +float phong_glossiness(float exponent) +{ + return (1.0 - phong_roughness(exponent)) * max(abs(param[1].w), 1.0); +} + +// the reflection of the surroundings of a material without a specgloss map is a smooth varnish over the rough surface: the +// old renderer showed it as a sharp reflection, as much of it as reflection: (and the masks of the material) asked for +bool legacy_coat = false; + +// set up the gloss of a material without a specgloss map, before apply_lights() +void legacy_gloss() +{ + glossiness = phong_glossiness(abs(param[1].w)); + legacy_coat = true; +} + +// Returns vec2(diffuse, specular) for a single punctual light. +// +// diffuse – Lambert N·L (Fresnel-weighted diffuse is handled per-material +// in apply_lights, so we return raw N·L here). +// specular – Cook-Torrance GGX: D·G / (4·NdotL·NdotV). +// The Fresnel factor (F) is intentionally omitted here; +// apply_lights already carries a per-material Fresnel term +// that is applied to env reflections and can be routed to +// direct specular there. +// +// Roughness is derived identically to env_roughness in apply_lights so +// that direct and indirect specular highlights read consistently. +vec2 calc_light(vec3 light_dir, vec3 fragnormal) +{ + vec3 N = fragnormal; + vec3 L = light_dir; + vec3 V = normalize(-f_pos.xyz); + vec3 H = normalize(L + V); + + float NdotL = max(dot(N, L), 0.0); + float NdotV = max(dot(N, V), 1e-4); + float NdotH = max(dot(N, H), 0.0); + + float diffuse_v = NdotL; + + // Mirror the env-map roughness derivation so direct and indirect lobes match. + // glossiness == param[1].w → roughness == 0.04 (near-mirror) + // glossiness == 0 → roughness == 1.0 (fully diffuse) + float roughness = clamp(1.0 - glossiness / max(abs(param[1].w), 1.0), 0.04, 1.0); + + // Cook-Torrance specular (no Fresnel — see above): + // f_spec = D(N,H,α) · G(N,V,L,α) / (4 · NdotL · NdotV) + float D = D_GGX(NdotH, roughness); + float G = G_Smith(NdotV, NdotL, roughness); + float specular_v = (NdotL > 0.0) + ? (D * G) / max(4.0 * NdotL * NdotV, 1e-4) + : 0.0; + + return vec2(diffuse_v, specular_v); +} + +vec2 calc_point_light(light_s light, vec3 fragnormal) +{ + vec3 light_dir = normalize(light.pos - f_pos.xyz); + vec2 val = calc_light(light_dir, fragnormal); + val.x += light.ambient; + val *= light.intensity; + + float distance = length(light.pos - f_pos.xyz); + float atten = 1.0f / (distance * distance); + //float atten = 1.0f / (1.0f + light.linear * distance + light.quadratic * (distance * distance)); + + return val * atten; +} + +vec2 calc_spot_light(light_s light, vec3 fragnormal) +{ + vec3 light_dir = normalize(light.pos - f_pos.xyz); + + float theta = dot(light_dir, normalize(-light.dir)); + float epsilon = light.in_cutoff - light.out_cutoff; + float intensity = clamp((theta - light.out_cutoff) / epsilon, 0.0, 1.0); + + vec2 point = calc_point_light(light, fragnormal); + return point * intensity; +} + +vec2 calc_dir_light(light_s light, vec3 fragnormal) +{ + vec3 light_dir = normalize(-light.dir); + return calc_light(light_dir, fragnormal); +} + +// samples of the soft shadows of the lamps and headlights: a disc, rotated per pixel +const vec2 SHADOW_DISC[8] = vec2[](vec2(-0.326, -0.406), vec2(-0.840, -0.074), vec2(-0.696, 0.457), vec2(-0.203, 0.621), + vec2(0.962, -0.195), vec2(0.473, -0.480), vec2(0.519, 0.767), vec2(0.185, -0.893)); + +float shadow_disc_angle() +{ + return 6.2831853 * fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715)))); +} + +// share of the beam of headlights with a shadow map (Map, headlight_weights.w - 1 of the light) held back before the fragment, +// 0..1. the map was drawn from the projector of the beam: x and y of the projection across it, z / w the depth (near plane over +// the distance along the axis, as the reverse z of the scene), w the distance. the comparison gives 1 where the map holds a +// value at least as large as the reference: a surface closer to the projector +float headlight_beam_shadow(uint map, vec3 fragnormal, vec3 light_dir) +{ + mat4 tomap = headlight_shadow[map]; + vec4 params = headlight_shadow_params[map]; + // a texel of the map is as large as this here. the point is moved along the normal by about a texel, and the reference towards + // the projector by a part of one. only the back faces are in the map, which leaves the lit faces little to shadow themselves with + float texel = params.x * max((tomap * vec4(f_pos.xyz, 1.0)).w, 0.0); + float NdotL = clamp(dot(fragnormal, light_dir), 0.0, 1.0); + vec4 position = tomap * vec4(f_pos.xyz + fragnormal * (texel * (0.5 + 1.5 * (1.0 - NdotL))), 1.0); + if (position.w <= 0.0) + return 0.0; + vec2 coords = position.xy / position.w; + if (any(lessThan(coords, vec2(0.0))) || any(greaterThan(coords, vec2(1.0)))) + return 0.0; + // nearer to the projector than its near plane (the vehicle itself) the reference is past anything in the map + float reference = position.z / max(position.w - texel * 1.5, 1e-3); + float layer = float(map); + float radius = params.y; + if (radius <= 0.0) + return texture(headlight_shadowmap, vec4(coords, layer, reference)); + float angle = shadow_disc_angle(); + vec2 rotation = vec2(cos(angle), sin(angle)); + float spread = radius / float(textureSize(headlight_shadowmap, 0).x); + int count = (radius > 1.5) ? 8 : 4; + float shadow = 0.0; + for (int i = 0; i < count; i++) + { + vec2 offset = vec2(SHADOW_DISC[i].x * rotation.x - SHADOW_DISC[i].y * rotation.y, SHADOW_DISC[i].x * rotation.y + SHADOW_DISC[i].y * rotation.x) * spread; + shadow += texture(headlight_shadowmap, vec4(coords + offset, layer, reference)); + } + return shadow / float(count); +} + +// dpdx and dpdy are the change of the view space position of the fragment from a pixel to the next one. the beam texture is +// sampled with gradients worked out from them instead of the ones of the hardware: those are undefined where the fragments of +// a quad do not go through the same lights, which is what happens at the edges of the clusters +vec2 calc_headlights(light_s light, vec3 fragnormal, vec3 dpdx, vec3 dpdy) +{ + vec4 headlightpos = light.headlight_projection * f_pos; + vec3 coords = headlightpos.xyz / headlightpos.w; + + if (coords.z > 1.0) + return vec2(0.0); + if (coords.z < 0.0) + return vec2(0.0); + // the beam is what the projection takes in. beyond its sides the texture would be clamped to its edge, and near the projector + // (where a pixel spans many texels) its coarsest levels spill the average of the whole beam over the surroundings + if (coords.x < 0.0 || coords.x > 1.0 || coords.y < 0.0 || coords.y > 1.0) + return vec2(0.0); + + vec3 light_dir = normalize(light.pos - f_pos.xyz); + // Tighter wrap (was +0.25): faces angled away from the headlight cone + // fall off to dark much faster, so cab/exterior surfaces read with a + // clear directional shape instead of a flat half-lit wash. + vec2 part = vec2(1.0) * clamp(dot(fragnormal, light_dir) + 0.10, 0.0, 1.0); + float distance = length(light.pos - f_pos.xyz); + float atten = 1.0f / (1.0f + light.linear * distance + light.quadratic * (distance * distance)); + atten *= mix(1.0, 0.0, clamp((coords.z - 0.998) * 500.0, 0.0, 1.0)); + vec2 beamcoords = headlightpos.xy / headlightpos.w; + vec4 dheadlightposdx = light.headlight_projection * vec4(dpdx, 0.0); + vec4 dheadlightposdy = light.headlight_projection * vec4(dpdy, 0.0); + vec2 dbeamdx = (dheadlightposdx.xy - beamcoords * dheadlightposdx.w) / headlightpos.w; + vec2 dbeamdy = (dheadlightposdy.xy - beamcoords * dheadlightposdy.w) / headlightpos.w; + vec3 lights = textureGrad(headlightmap, beamcoords, dbeamdx, dbeamdy).rgb * light.headlight_weights.rgb; + float lightintensity = max(max(lights.r, lights.g), lights.b); + // headlights with a shadow map: what is in the way holds the beam back + if ((light.headlight_weights.w > 0.5) && (lightintensity > 0.0) && (part.x > 0.0)) + lightintensity *= 1.0 - headlight_beam_shadow(uint(light.headlight_weights.w - 0.5), fragnormal, light_dir); + if ((contact_shadow_budget > 0) && (lightintensity > 0.0) && (part.x > 0.0)) + lightintensity *= 1.0 - contact_shadow_lamp(light_dir, distance, + part.x * atten * lightintensity * light.intensity * max(light.color.r, max(light.color.g, light.color.b))); + // x the diffuse light, y the light the specular reflection takes (specular_light() brings its own n.l) + return vec2(part.x, 1.0) * (atten * lightintensity); +} + +// closest distance (squared) the light of a lamp is computed for: nearer the inverse square would grow without a limit +const float LOCAL_LIGHT_MIN_DISTANCE2 = 0.01; + +// one comparison with the cube shadow map at the point of the space of the map, moved towards the lamp by Bias (metres). the +// map is in the array of the scenery lamps when Lamps, in the one of the cab lamp otherwise +float local_light_shadow_tap(vec3 position, float layer, float near, float bias, bool lamps) +{ + vec3 axes = abs(position); + float major = max(axes.x, max(axes.y, axes.z)); + float reference = near / max(major - bias, near); + return lamps ? texture(local_shadowmap_lamps, vec4(position, layer), reference) : texture(local_shadowmap, vec4(position, layer), reference); +} + +// share of the light of a lamp with a cube shadow map (headlight_weights.x is its layer) which reaches the fragment, 0..1. +// the map holds n / d of the closest surface, d the distance along the axis of the face, n the near plane: the farther, the +// smaller (as the depth of the scene, reverse z). the comparison gives 1 where the map holds a value at least as large as +// the reference, i.e. a surface at most as far: in shadow +float local_light_shadow(light_s light, vec3 fragnormal) +{ + // the space of the map: origin at the lamp, axes of the model the shadows are cast by + vec3 position = (light.headlight_projection * vec4(f_pos.xyz, 1.0)).xyz; + vec3 normal = normalize(mat3(light.headlight_projection) * fragnormal); + float distance = length(position); + // a texel of the map is as large as this here. the point is moved along the normal by about a texel, which keeps a surface + // from shadowing itself at grazing angles, and the reference is moved towards the lamp by a part of a texel + float texel = light.headlight_weights.z * distance; + vec3 tolamp = -position / max(distance, 1e-4); + float NdotL = clamp(dot(normal, tolamp), 0.0, 1.0); + position += normal * (texel * (0.5 + 1.5 * (1.0 - NdotL))); + float bias = texel * 1.5; + float layer = light.headlight_weights.x; + float radius = light.headlight_weights.w; + bool lamps = (light.linear > 0.5); + if (radius <= 0.0) + return local_light_shadow_tap(position, layer, light.headlight_weights.y, bias, lamps); + // a disc of samples in the plane of the surface, each compared with its own distance: points of the surface itself, which + // samples spread across the direction to the lamp would not be at grazing angles. rotated per pixel to turn the banding into noise + vec3 side = normalize(cross(normal, (abs(normal.y) < 0.99) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0))); + vec3 up = cross(normal, side); + float angle = shadow_disc_angle(); + vec2 rotation = vec2(cos(angle), sin(angle)); + float spread = texel * radius; + int count = (radius > 1.5) ? 8 : 4; + float shadow = 0.0; + for (int i = 0; i < count; i++) + { + vec2 offset = vec2(SHADOW_DISC[i].x * rotation.x - SHADOW_DISC[i].y * rotation.y, SHADOW_DISC[i].x * rotation.y + SHADOW_DISC[i].y * rotation.x) * spread; + shadow += local_light_shadow_tap(position + side * offset.x + up * offset.y, layer, light.headlight_weights.y, bias, lamps); + } + return shadow / float(count); +} + +// light of a lamp (point or spot light: the lamps of models and vehicles, the cab lamp) reaching the fragment, x the diffuse +// part and y the light the specular reflection takes (specular_light() brings its own n.l). it falls off with the square of the distance, and smoothly to nothing at the range of the light, +// 1 / sqrt(quadratic), past which the culling of the clusters does not take it. a spot light fades from its inner cone to the +// outer one. a lamp with a shadow map is held back by what is between it and the fragment +vec2 calc_local_light(light_s light, vec3 fragnormal) +{ + vec3 tolight = light.pos - f_pos.xyz; + float distance2 = dot(tolight, tolight); + vec3 light_dir = tolight * inversesqrt(max(distance2, 1e-8)); + float edge = clamp(1.0 - distance2 * distance2 * (light.quadratic * light.quadratic), 0.0, 1.0); + float atten = edge * edge / max(distance2, LOCAL_LIGHT_MIN_DISTANCE2); + if (light.type == LIGHT_SPOT) + atten *= smoothstep(light.out_cutoff, light.in_cutoff, dot(-light_dir, normalize(light.dir))); + if ((atten > 0.0) && (light.headlight_weights.x >= 0.0) && (dot(fragnormal, light_dir) > 0.0)) + atten *= 1.0 - local_light_shadow(light, fragnormal); + if ((atten > 0.0) && (contact_shadow_budget > 0)) + atten *= 1.0 - contact_shadow_lamp(light_dir, sqrt(distance2), + atten * max(dot(fragnormal, light_dir), 0.0) * light.intensity * max(light.color.r, max(light.color.g, light.color.b))); + if (atten <= 0.0) + return vec2(0.0); + return vec2(max(dot(fragnormal, light_dir), 0.0), 1.0) * atten; +} + +// index of the light cluster the fragment is in, see gl/cluster_grid.h: its screen tile and slice of depth. the depth is the clip +// space w, which is 1 / gl_FragCoord.w. a tile is kept in the grid, as the far edge of the render target and rounding +// right below it give one past the last +uint fragment_cluster() +{ + ivec2 tile = ivec2(gl_FragCoord.xy * (vec2(CLUSTER_GRID_X, CLUSTER_GRID_Y) / cluster_viewport_size)); + tile = clamp(tile, ivec2(0), ivec2(CLUSTER_GRID_X - 1, CLUSTER_GRID_Y - 1)); + int slice = clamp(int(floor(cluster_slice_bias - log(gl_FragCoord.w) * cluster_slice_scale)), 0, CLUSTER_GRID_Z - 1); + return uint(tile.x + CLUSTER_GRID_X * (tile.y + CLUSTER_GRID_Y * slice)); +} + +// adds the light of a headlight or lamp to the diffuse and the specular light of the fragment +void add_local_light(light_s light, vec2 part, vec3 fragnormal, inout vec3 diffuse, inout vec3 specular) +{ + if (part.y <= 0.0) + return; + vec3 color = light.color * light.intensity; + diffuse += color * part.x; + if (matte) + return; + specular += color * (part.y * param[1].y) * specular_light(normalize(light.pos - f_pos.xyz), fragnormal, spec_f0, spec_roughness, LOCAL_LIGHT_BRDF_LIMIT); +} + +// adds the light of the headlights and lamps which reach the fragment to its diffuse and specular light. these are the ones in +// the list of its cluster when the lights are clustered (the sun is not on the list, the callers take care of it), and the +// first lights_count - 1 of lights[] otherwise, which are headlights only. the specular light is reflected with spec_f0 and +// spec_roughness, and is already coloured by the fresnel term +void add_local_lights(vec3 fragnormal, inout vec3 diffuse, inout vec3 specular) +{ + // worked out before the loops, which the fragments of a quad do not leave together + vec3 dpdx = dFdx(f_pos.xyz); + vec3 dpdy = dFdy(f_pos.xyz); + contact_shadow_budget = (contact_shadow_params.x > 0.0) ? int(contact_shadow_lamps.x) : 0; + + if (lights_clustered != 0U) + { + cluster_range_s range = cluster_ranges[fragment_cluster()]; + for (uint i = 0U; i < range.count; i++) + { + light_s light = lights[cluster_light_indices[range.offset + i]]; + vec2 part = (light.type == LIGHT_HEADLIGHTS) ? calc_headlights(light, fragnormal, dpdx, dpdy) : calc_local_light(light, fragnormal); + add_local_light(light, part, fragnormal, diffuse, specular); + } + } + else + { + for (uint i = 1U; i < lights_count; i++) + { + light_s light = lights[i]; + add_local_light(light, calc_headlights(light, fragnormal, dpdx, dpdy), fragnormal, diffuse, specular); + } + } +} + +// the light of the headlights and lamps added to Fragcolor, diffuse and specular together (for shaders which don't tell +// the albedo from the reflection) +vec3 add_headlights(vec3 fragcolor, vec3 fragnormal) +{ + vec3 diffuse = vec3(0.0); + vec3 specular = vec3(0.0); + add_local_lights(fragnormal, diffuse, specular); + return fragcolor + diffuse + specular; +} + +// ----------------------------------------------------------------------- +// Split-sum environment BRDF (Karis / UE4 analytic approximation). +// +// This is the missing piece that made matte specgloss surfaces "shine like +// crazy": previously the env reflection was added at full strength +// (envcolor * fresnel * reflectivity) and roughness only blurred the mip, +// never dimmed the energy. A rough surface therefore mirrored the bright +// sky just as strongly as a polished one. +// +// EnvBRDFApprox returns the pre-integrated specular scale (the "DFG" term) +// for a given F0, roughness and view angle. For rough surfaces it collapses +// toward ~0, so low-glossiness materials reflect almost nothing — matching +// what Substance 3D Painter shows. Polished surfaces keep their full +// reflection, and the grazing-angle Fresnel edge is preserved. +// roughness 1.0 (matte) -> scale ~0.015 (virtually no reflection) +// roughness 0.0 (mirror) -> scale ~F0..1 (full reflection + Fresnel rim) +vec3 EnvBRDFApprox(vec3 F0, float roughness, float NoV) +{ + const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022); + const vec4 c1 = vec4( 1.0, 0.0425, 1.040, -0.040); + vec4 r = roughness * c0 + c1; + float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y; + vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw; + return F0 * AB.x + AB.y; +} + +// Diffuse colour of the submodel (t3d/e3d) or shape, used as the albedo multiplier: +// untextured materials take it as their colour, textured ones multiply the base texture by it +// (same as the legacy renderer's GL_MODULATE). param[0].rgb holds the authored sRGB colour, +// param[1].x its intensity (HSV value unless a material overrides it). +vec3 material_diffuse() +{ + vec3 diffusecolor = pow(max(param[0].rgb, vec3(0.0)), vec3(2.2)); + float value = max(param[0].r, max(param[0].g, param[0].b)); + return (value > 0.0 ? diffusecolor * (param[1].x / value) : vec3(0.0)); +} + +// directional part of the sky ambient light (sky_ssbo, bands 1 and 2 of the spherical harmonics) for a world space direction +vec3 sky_ambient_directional(vec3 n) +{ + return sky_ambient_sh[1].rgb * (0.488603 * n.y) + + sky_ambient_sh[2].rgb * (0.488603 * n.z) + + sky_ambient_sh[3].rgb * (0.488603 * n.x) + + sky_ambient_sh[4].rgb * (1.092548 * n.x * n.y) + + sky_ambient_sh[5].rgb * (1.092548 * n.y * n.z) + + sky_ambient_sh[6].rgb * (0.315392 * (3.0 * n.z * n.z - 1.0)) + + sky_ambient_sh[7].rgb * (1.092548 * n.x * n.z) + + sky_ambient_sh[8].rgb * (0.546274 * (n.x * n.x - n.y * n.y)); +} + +// adds the directional part of the sky light to the ambient light the material starts from (the uniform ambient, or a share +// of it, like the water takes). the same share of the directional part is added, so with the directional part at zero +// (gfx.ambient.sh off) the result is what it always was +vec3 add_sky_ambient(vec3 basecolor, vec3 fragnormal) +{ + if (sky_ambient_factor == 0.0) + return basecolor; + float share = dot(basecolor, vec3(1.0)) / max(dot(ambient, vec3(1.0)), 1e-4); + vec3 worldnormal = normalize(mat3(inv_view) * fragnormal); + return max(basecolor + sky_ambient_directional(worldnormal) * (sky_ambient_factor * share), vec3(0.0)); +} + +// lights the surface: the ambient light, the sun (the moon), the headlights and lamps and the reflection of the surroundings. +// the units are the ones of the ambient light, the irradiance over pi: a white lambertian surface lit by 1 of it shows 1. +// Texturecolor is the albedo (the base colour of a metal), Specularity the specular level of a dielectric (0.5 is the +// reflectance of 4% at normal incidence), Reflectivity how much of the reflected surroundings is the cubemap rather than the +// ambient light, Shadowtone the share of the sun left in its shadow (1 for the materials which take no shadows). +// a Sunless surface (the interior of a cab) gets no light of the sun, its shadow only darkens the ambient light +vec3 shade_surface(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone, bool sunless) +{ + // Diffuse colour is applied once, to the albedo; the light terms below are not scaled by it again + texturecolor *= material_diffuse(); + float polished = 0.0; + if (sunless) + { + // Cab interior: dim ambient less than the exterior path - ambient + // is the dominant indoor illumination, but it was still too bright. + fragcolor *= 0.80; + } + else + { + // the sky lights the faces turned up and towards the bright part of the sky more than the ones turned to the ground + fragcolor = add_sky_ambient(fragcolor, fragnormal); +#ifndef NO_WEATHERING + float weathered = (param[2].w > 0.0 ? clamp(param[2].w * UserData.x, 0.0, 1.0) : 0.0); + float tinted = (param[2].w < 0.0 ? clamp(-param[2].w * UserData.x, 0.0, 1.0) : 0.0); + // a tint only scales the colour of the texture, the steel keeps its own polish + polished = weathered * smoothstep(0.03, 0.12, dot(texturecolor, vec3(0.2126, 0.7152, 0.0722))); + float weatheredluma = min(dot(texturecolor, vec3(0.2126, 0.7152, 0.0722)), 0.05); + vec3 unused = texturecolor * (weatheredluma / max(dot(texturecolor, vec3(0.2126, 0.7152, 0.0722)), 1e-4)); + unused = mix(unused, vec3(1.0, 0.82, 0.74) * (weatheredluma / 0.8525), 0.5); + texturecolor = mix(texturecolor, unused, smoothstep(0.0, 0.4, weathered)); + texturecolor = (tinted > 0.0 ? texturecolor * mix(vec3(1.0), param[2].rgb, tinted) : texturecolor); + specularity = mix(specularity, min(specularity, 0.03), smoothstep(0.0, 0.4, polished)); + metalic = mix(metalic, 0.0, polished); + glossiness = mix(glossiness, min(glossiness, 0.06 * abs(param[1].w)), polished); + reflectivity = mix(reflectivity, min(reflectivity, 0.08 * param[1].z), smoothstep(0.0, 0.4, polished)); +#endif + // Scale ambient before it gets tinted by the texture. + // Sun, headlights and emission are added afterwards so they are NOT + // attenuated by AMBIENT_SCALE - this only dims the indirect term. + fragcolor *= AMBIENT_SCALE; + } + + vec3 emissioncolor = vec3(emission); + vec3 view_dir = normalize(-f_pos.xyz); + float NdotV = max(dot(fragnormal, view_dir), 1e-4); + + // glossiness == param[1].w -> mirror, glossiness == 0 -> fully rough + float roughness = clamp(1.0 - glossiness / max(abs(param[1].w), 1.0), 0.0, 1.0); + roughness = max(specular_antialiasing(fragnormal, roughness), MIN_ROUGHNESS); + // metals reflect with their base colour, dielectrics with the specular level (0.5 -> 0.04) + vec3 f0 = mix(vec3(0.08 * specularity), texturecolor, metalic); + spec_f0 = f0; + spec_roughness = roughness; + + // Pre-integrated env BRDF: how much of the surroundings the surface reflects, for its F0, roughness and the view angle. + // the surroundings are the cubemap where the material asks for it (reflectivity) and the ambient light elsewhere, so a + // surface reflects them always, the rough one barely + vec3 env_spec = EnvBRDFApprox(f0, roughness, NdotV); + float env_roughness = roughness; + vec3 cubemap_spec = env_spec; + float cubemap_share = max(reflectivity, 0.0); + float ambient_share = clamp(1.0 - reflectivity, 0.0, 1.0); + if (matte) + { + env_spec = vec3(0.0); + cubemap_share = 0.0; + } + // what the diffuse layer is left with + vec3 diffuse_albedo = texturecolor * (1.0 - metalic) * (1.0 - env_spec); + if (legacy_coat) + { + // the varnish: a smooth dielectric layer reflecting the cubemap, worn off where the material is weathered + env_roughness = specular_antialiasing(fragnormal, roughness * smoothstep(0.0, 0.4, polished)); + cubemap_spec = EnvBRDFApprox(mix(vec3(0.04), texturecolor, metalic), env_roughness, NdotV); + ambient_share = 1.0; + diffuse_albedo *= 1.0 - cubemap_spec * min(cubemap_share, 1.0); + } + vec3 envcolor = (matte ? vec3(0.0) : envmap_color_rough(fragnormal, env_roughness)); + + if (lights_count == 0U) + return fragcolor * diffuse_albedo + + emissioncolor * texturecolor + + fragcolor * env_spec * ambient_share + + envcolor * cubemap_spec * cubemap_share; + + vec3 light_dir = normalize(-lights[0].dir); + float NdotL = max(dot(fragnormal, light_dir), 0.0); + // Sharpen sun N.L falloff so the lit-to-shaded terminator on cab + // panels, vehicle bodies and terrain reads as a clear edge rather + // than a soft Lambertian ramp. Tunable via SUN_NDOTL_SHARPNESS. + float sun_NdotL = pow(NdotL, SUN_NDOTL_SHARPNESS); + // the clouds hold back the sun (the moon at night) where their shadow falls + float cloudsun = cloud_shadow(mat3(inv_view) * f_pos.xyz); + // Sun HDR scale -> SUN_DIFFUSE_SCALE, the same for the diffuse and the specular light + vec3 sunlight = lights[0].color * (SUN_DIFFUSE_SCALE * lights[0].intensity * cloudsun); + + float shadow = 0.0; + if (shadowtone < 1.0) + shadow = clamp(calc_shadow(), 0.0, 1.0); + + // the cubemap knows nothing of what shades the fragment: it is dimmed a little in the shadow of the sun + float cubemap_shadow = 1.0; + if (sunless) + { + // the sun adds no light in the cab, where it would fall its shadow darkens the ambient light + float diffuseamount = sun_NdotL * lights[0].intensity; + float darkness = (shadowtone < 1.0 ? max(shadow, 1.0 - cloudsun) : 0.0); + fragcolor = mix(fragcolor, fragcolor * shadowtone, clamp(diffuseamount * darkness, 0.0, 1.0)); + } + else + cubemap_shadow = 1.0 - (1.0 - shadowtone) * shadow * 0.5; + + // the reflection of the surroundings, before the light of the sun and the lamps joins the ambient light + vec3 env_reflection = fragcolor * env_spec * ambient_share + + envcolor * cubemap_spec * (cubemap_share * cubemap_shadow); + + // the shadow leaves the share of the sun Shadowtone tells (the old shadow colour of the scenery) to the diffuse light, + // nothing to the highlight + if (!sunless) + fragcolor += sunlight * (sun_NdotL * (1.0 - (1.0 - shadowtone) * shadow)); + vec3 specular = matte ? vec3(0.0) : sunlight * specular_light(light_dir, fragnormal, f0, roughness, SUN_BRDF_LIMIT) * (1.0 - shadow); + + vec3 lampspecular = vec3(0.0); + add_local_lights(fragnormal, fragcolor, lampspecular); + + return fragcolor * diffuse_albedo // ambient, sun and lamps on the diffuse layer + + specular * param[1].y // highlight of the sun, fresnel coloured (metals take their base colour) + + lampspecular // highlights of the lamps, param[1].y already applied + + emissioncolor * texturecolor + + env_reflection; +} + +vec3 apply_lights(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone) +{ + return shade_surface(fragcolor, fragnormal, texturecolor, reflectivity, specularity, shadowtone, false); +} + +// the interior of a cab: no light of the sun, its shadow darkens the ambient light +vec3 apply_lights_sunless(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor, float reflectivity, float specularity, float shadowtone) +{ + return shade_surface(fragcolor, fragnormal, texturecolor, reflectivity, specularity, shadowtone, true); +} diff --git a/shaders/map.frag b/shaders/ogl46/map.frag similarity index 60% rename from shaders/map.frag rename to shaders/ogl46/map.frag index b3ec007a..5bd32bca 100644 --- a/shaders/map.frag +++ b/shaders/ogl46/map.frag @@ -1,4 +1,6 @@ -#include +#version 460 core +#define STAGE_FRAGMENT 1 +#include "common.glsl" layout(location = 0) out vec4 out_color; diff --git a/shaders/map.vert b/shaders/ogl46/map.vert similarity index 67% rename from shaders/map.vert rename to shaders/ogl46/map.vert index 149efb62..82f1535a 100644 --- a/shaders/map.vert +++ b/shaders/ogl46/map.vert @@ -1,9 +1,11 @@ +#version 460 core +#define STAGE_VERTEX 1 layout(location = 0) in vec3 v_vert; layout(location = 2) in vec2 v_coords; -out vec2 g_coords; +layout(location = 19) out vec2 g_coords; -#include +#include "common.glsl" void main() { diff --git a/shaders/ogl46/map_poi.frag b/shaders/ogl46/map_poi.frag new file mode 100644 index 00000000..bcf086d0 --- /dev/null +++ b/shaders/ogl46/map_poi.frag @@ -0,0 +1,16 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 14) in vec2 f_coords; + +layout(location = 0) out vec4 out_color; + +#pragma texture(tex1, 0, sRGB_A) +layout(binding = 0) uniform sampler2D tex1; + +void main() +{ + vec2 texcoord = f_coords; + vec4 color = texture(tex1, texcoord); + + out_color = color; +} diff --git a/shaders/map_poi.geom b/shaders/ogl46/map_poi.geom similarity index 81% rename from shaders/map_poi.geom rename to shaders/ogl46/map_poi.geom index aaa336b9..1ff4a941 100644 --- a/shaders/map_poi.geom +++ b/shaders/ogl46/map_poi.geom @@ -1,10 +1,12 @@ +#version 460 core +#define STAGE_GEOMETRY 1 layout (points) in; layout (triangle_strip, max_vertices = 4) out; -in vec2 g_coords[]; -out vec2 f_coords; +layout(location = 19) in vec2 g_coords[]; +layout(location = 14) out vec2 f_coords; -#include +#include "common.glsl" void main() { vec4 position = gl_in[0].gl_Position; diff --git a/shaders/mat_clouds.frag b/shaders/ogl46/mat_clouds.frag similarity index 60% rename from shaders/mat_clouds.frag rename to shaders/ogl46/mat_clouds.frag index 5e6f4f39..9179fa7c 100644 --- a/shaders/mat_clouds.frag +++ b/shaders/ogl46/mat_clouds.frag @@ -1,24 +1,24 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include +#include "common.glsl" -#param (diffuse, 1, 0, 1, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) -#texture (diffuse, 0, sRGB_A) -uniform sampler2D diffuse; +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED layout(location = 1) out vec4 out_motion; -#endif -#include -#include +#include "apply_fog.glsl" +#include "tonemapping.glsl" vec3 apply_lights_clouds(vec3 fragcolor, vec3 fragnormal, vec3 texturecolor) { @@ -49,13 +49,17 @@ void main() fragcolor = apply_lights_clouds(fragcolor, fragnormal, tex_color.rgb); vec4 color = vec4(apply_fog(fragcolor), tex_color.a); -#if POSTFX_ENABLED - out_color = color; -#else - out_color = tonemap(color); -#endif + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } -#if MOTIONBLUR_ENABLED - out_motion = vec4(0.0f); -#endif + if (MOTIONBLUR_ENABLED) + { + out_motion = vec4(0.0f); + } } diff --git a/shaders/ogl46/mat_colored.frag b/shaders/ogl46/mat_colored.frag new file mode 100644 index 00000000..a02645b7 --- /dev/null +++ b/shaders/ogl46/mat_colored.frag @@ -0,0 +1,3 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +#include "mat_colored.glsl" diff --git a/shaders/mat_colored.frag b/shaders/ogl46/mat_colored.glsl similarity index 56% rename from shaders/mat_colored.frag rename to shaders/ogl46/mat_colored.glsl index 5f5dd78a..6dfaa229 100644 --- a/shaders/mat_colored.frag +++ b/shaders/ogl46/mat_colored.glsl @@ -1,26 +1,24 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include +#include "common.glsl" -#param (color, 0, 0, 4, diffuse) -#param (diffuse, 1, 0, 1, diffuse) -#param (specular, 1, 1, 1, specular) -#param (reflection, 1, 2, 1, zero) -#param (glossiness, 1, 3, 1, glossiness) +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, zero) +#pragma param(glossiness, 1, 3, 1, glossiness) layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED layout(location = 1) out vec4 out_motion; -#endif -#include -#include -#include +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" void main() { @@ -39,17 +37,19 @@ void main() 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 + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } + 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 } diff --git a/shaders/ogl46/mat_default.frag b/shaders/ogl46/mat_default.frag new file mode 100644 index 00000000..45a5ebc8 --- /dev/null +++ b/shaders/ogl46/mat_default.frag @@ -0,0 +1,3 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +#include "mat_default.glsl" diff --git a/shaders/mat_default.frag b/shaders/ogl46/mat_default.glsl similarity index 57% rename from shaders/mat_default.frag rename to shaders/ogl46/mat_default.glsl index 28bc8c68..f0381360 100644 --- a/shaders/mat_default.frag +++ b/shaders/ogl46/mat_default.glsl @@ -1,29 +1,27 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include +#include "common.glsl" -#param (color, 0, 0, 4, diffuse) -#param (diffuse, 1, 0, 1, diffuse) -#param (specular, 1, 1, 1, specular) -#param (reflection, 1, 2, 1, zero) -#param (glossiness, 1, 3, 1, glossiness) +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, zero) +#pragma param(glossiness, 1, 3, 1, glossiness) -#texture (diffuse, 0, sRGB_A) -uniform sampler2D diffuse; +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED layout(location = 1) out vec4 out_motion; -#endif -#include -#include -#include +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" void main() { @@ -47,18 +45,20 @@ void main() else if( distance <= cascade_end.y ) { color.g += 0.25; } else if( distance <= cascade_end.z ) { color.b += 0.25; } */ -#if POSTFX_ENABLED - out_color = color; -#else - out_color = tonemap(color); -#endif + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } -#if MOTIONBLUR_ENABLED + 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, tex_color.a * alpha_mult); } -#endif } diff --git a/shaders/ogl46/mat_default_0.frag b/shaders/ogl46/mat_default_0.frag new file mode 100644 index 00000000..a02645b7 --- /dev/null +++ b/shaders/ogl46/mat_default_0.frag @@ -0,0 +1,3 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +#include "mat_colored.glsl" diff --git a/shaders/ogl46/mat_default_1.frag b/shaders/ogl46/mat_default_1.frag new file mode 100644 index 00000000..45a5ebc8 --- /dev/null +++ b/shaders/ogl46/mat_default_1.frag @@ -0,0 +1,3 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +#include "mat_default.glsl" diff --git a/shaders/ogl46/mat_default_2.frag b/shaders/ogl46/mat_default_2.frag new file mode 100644 index 00000000..69b37cec --- /dev/null +++ b/shaders/ogl46/mat_default_2.frag @@ -0,0 +1,3 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +#include "mat_reflmap.glsl" diff --git a/shaders/mat_default_detail.frag b/shaders/ogl46/mat_default_detail.frag similarity index 53% rename from shaders/mat_default_detail.frag rename to shaders/ogl46/mat_default_detail.frag index 5f85e129..8ca82f71 100644 --- a/shaders/mat_default_detail.frag +++ b/shaders/ogl46/mat_default_detail.frag @@ -1,35 +1,35 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; -in mat3 f_tbn; +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +layout(location = 8) in mat3 f_tbn; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include +#include "common.glsl" -#param (color, 0, 0, 4, diffuse) -#param (diffuse, 1, 0, 1, diffuse) -#param (specular, 1, 1, 1, specular) -#param (reflection, 1, 2, 1, zero) -#param (glossiness, 1, 3, 1, glossiness) -#param (detail_scale, 2, 0, 1, one) +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, zero) +#pragma param(glossiness, 1, 3, 1, glossiness) +#pragma param(detail_scale, 2, 0, 1, one) -#texture (diffuse, 0, sRGB_A) -uniform sampler2D diffuse; +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED layout(location = 1) out vec4 out_motion; -#endif -#texture (detailnormalmap, 1, RGBA) -uniform sampler2D detailnormalmap; +#pragma texture(detailnormalmap, 1, RGBA) +layout(binding = 1) uniform sampler2D detailnormalmap; #define NORMALMAP -#include -#include -#include +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" void main() { @@ -53,18 +53,20 @@ void main() 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 (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } -#if MOTIONBLUR_ENABLED + 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, tex_color.a * alpha_mult); } -#endif } diff --git a/shaders/mat_default_specgloss.frag b/shaders/ogl46/mat_default_specgloss.frag similarity index 57% rename from shaders/mat_default_specgloss.frag rename to shaders/ogl46/mat_default_specgloss.frag index 254a0cc7..64afc53d 100644 --- a/shaders/mat_default_specgloss.frag +++ b/shaders/ogl46/mat_default_specgloss.frag @@ -1,32 +1,32 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include +#include "common.glsl" -#param (color, 0, 0, 4, diffuse) -#param (diffuse, 1, 0, 1, diffuse) -#param (specular, 1, 1, 1, specular) -#param (reflection, 1, 2, 1, zero) -#param (glossiness, 1, 3, 1, glossiness) +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, zero) +#pragma param(glossiness, 1, 3, 1, glossiness) -#texture (diffuse, 0, sRGB_A) -uniform sampler2D diffuse; +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; -#texture (specgloss, 1, RGBA) -uniform sampler2D specgloss; +#pragma texture(specgloss, 1, RGBA) +layout(binding = 1) uniform sampler2D specgloss; layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED layout(location = 1) out vec4 out_motion; -#endif -#include -#include -#include +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" void main() { @@ -53,18 +53,20 @@ void main() else if( distance <= cascade_end.y ) { color.g += 0.25; } else if( distance <= cascade_end.z ) { color.b += 0.25; } */ -#if POSTFX_ENABLED - out_color = color; -#else - out_color = tonemap(color); -#endif + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } -#if MOTIONBLUR_ENABLED + 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, tex_color.a * alpha_mult); } -#endif } diff --git a/shaders/mat_detail_normalmap.frag b/shaders/ogl46/mat_detail_normalmap.frag similarity index 57% rename from shaders/mat_detail_normalmap.frag rename to shaders/ogl46/mat_detail_normalmap.frag index f46c6fea..7d847c1b 100644 --- a/shaders/mat_detail_normalmap.frag +++ b/shaders/ogl46/mat_detail_normalmap.frag @@ -1,82 +1,84 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; -in mat3 f_tbn; - -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; - -#include - -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) - -#texture (diffuse, 0, sRGB_A) -uniform sampler2D diffuse; - -#texture (normalmap, 1, RGBA) -uniform sampler2D normalmap; - -#texture (detailnormalmap, 2, RGBA) -uniform sampler2D detailnormalmap; - -#define NORMALMAP -#include -#include -#include - -void main() -{ - vec4 tex_color = texture(diffuse, f_coord); - - bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) ); - if(alphatestfail) - discard; - - vec3 fragcolor = ambient; - - vec4 normal_map = texture(normalmap, f_coord); - vec4 detailnormal_map = texture(detailnormalmap, f_coord * param[2].x); - 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 reflblend = (normal_map.a + detailnormal_map.a); - float reflectivity = reflblend > 1.0 ? param[1].z * 1.0 : param[1].z * reflblend; - 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 -} +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +layout(location = 8) in mat3 f_tbn; + +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; + +#include "common.glsl" + +layout(location = 0) out vec4 out_color; +layout(location = 1) out vec4 out_motion; + +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, one) +#pragma param(glossiness, 1, 3, 1, glossiness) +#pragma param(detail_scale, 2, 0, 1, one) +#pragma param(detail_height_scale, 2, 1, 1, one) + +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; + +#pragma texture(normalmap, 1, RGBA) +layout(binding = 1) uniform sampler2D normalmap; + +#pragma texture(detailnormalmap, 2, RGBA) +layout(binding = 2) uniform sampler2D detailnormalmap; + +#define NORMALMAP +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" + +void main() +{ + vec4 tex_color = texture(diffuse, f_coord); + + bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) ); + if(alphatestfail) + discard; + + vec3 fragcolor = ambient; + + vec4 normal_map = texture(normalmap, f_coord); + vec4 detailnormal_map = texture(detailnormalmap, f_coord * param[2].x); + 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 reflblend = (normal_map.a + detailnormal_map.a); + float reflectivity = reflblend > 1.0 ? param[1].z * 1.0 : param[1].z * reflblend; + 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); + } + 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); + } +} diff --git a/shaders/mat_detail_normalmap_specgloss.frag b/shaders/ogl46/mat_detail_normalmap_specgloss.frag similarity index 57% rename from shaders/mat_detail_normalmap_specgloss.frag rename to shaders/ogl46/mat_detail_normalmap_specgloss.frag index a3799ce1..93be19fa 100644 --- a/shaders/mat_detail_normalmap_specgloss.frag +++ b/shaders/ogl46/mat_detail_normalmap_specgloss.frag @@ -1,89 +1,91 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; -in mat3 f_tbn; - -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; - -#include - -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) - -#texture (diffuse, 0, sRGB_A) -uniform sampler2D diffuse; - -#texture (normalmap, 1, RGBA) -uniform sampler2D normalmap; - -#texture (detailnormalmap, 2, RGBA) -uniform sampler2D detailnormalmap; - -#texture (specgloss, 3, RGBA) -uniform sampler2D specgloss; - -#define NORMALMAP -#include -#include -#include - -void main() -{ - vec4 tex_color = texture(diffuse, f_coord); - - 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; - - vec4 normal_map = texture(normalmap, f_coord); - vec4 detailnormal_map = texture(detailnormalmap, f_coord * param[2].x); - vec4 specgloss_map = texture(specgloss, f_coord); - 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 reflblend = (normal_map.a + detailnormal_map.a); - float reflectivity = reflblend > 1.0 ? param[1].z * 1.0 : param[1].z * reflblend; - 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 -} +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +layout(location = 8) in mat3 f_tbn; + +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; + +#include "common.glsl" + +layout(location = 0) out vec4 out_color; +layout(location = 1) out vec4 out_motion; + +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, one) +#pragma param(glossiness, 1, 3, 1, glossiness) +#pragma param(detail_scale, 2, 0, 1, one) +#pragma param(detail_height_scale, 2, 1, 1, one) + +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; + +#pragma texture(normalmap, 1, RGBA) +layout(binding = 1) uniform sampler2D normalmap; + +#pragma texture(detailnormalmap, 2, RGBA) +layout(binding = 2) uniform sampler2D detailnormalmap; + +#pragma texture(specgloss, 3, RGBA) +layout(binding = 3) uniform sampler2D specgloss; + +#define NORMALMAP +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" + +void main() +{ + vec4 tex_color = texture(diffuse, f_coord); + + 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; + + vec4 normal_map = texture(normalmap, f_coord); + vec4 detailnormal_map = texture(detailnormalmap, f_coord * param[2].x); + vec4 specgloss_map = texture(specgloss, f_coord); + 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 reflblend = (normal_map.a + detailnormal_map.a); + float reflectivity = reflblend > 1.0 ? param[1].z * 1.0 : param[1].z * reflblend; + 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); + } + 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); + } +} diff --git a/shaders/ogl46/mat_detail_parallax.frag b/shaders/ogl46/mat_detail_parallax.frag new file mode 100644 index 00000000..399be55f --- /dev/null +++ b/shaders/ogl46/mat_detail_parallax.frag @@ -0,0 +1,147 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +layout(location = 8) in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; +layout(location = 6) in vec3 TangentFragPos; + +#include "common.glsl" + +layout(location = 0) out vec4 out_color; +layout(location = 1) out vec4 out_motion; + + +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, one) +#pragma param(glossiness, 1, 3, 1, glossiness) +#pragma param(detail_scale, 2, 0, 1, one) +#pragma param(detail_height_scale, 2, 1, 1, one) +#pragma param(height_scale, 2, 2, 1, zero) +#pragma param(height_offset, 2, 3, 1, zero) + +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; + +#pragma texture(normalmap, 1, RGBA) +layout(binding = 1) uniform sampler2D normalmap; + +#pragma texture(detailnormalmap, 2, RGBA) +layout(binding = 2) 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); + } + 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); + } +} + +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; + } +} diff --git a/shaders/ogl46/mat_detail_parallax_specgloss.frag b/shaders/ogl46/mat_detail_parallax_specgloss.frag new file mode 100644 index 00000000..735728e5 --- /dev/null +++ b/shaders/ogl46/mat_detail_parallax_specgloss.frag @@ -0,0 +1,153 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +layout(location = 8) in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; +layout(location = 6) in vec3 TangentFragPos; + +#include "common.glsl" + +layout(location = 0) out vec4 out_color; +layout(location = 1) out vec4 out_motion; + + +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, one) +#pragma param(glossiness, 1, 3, 1, glossiness) +#pragma param(detail_scale, 2, 0, 1, one) +#pragma param(detail_height_scale, 2, 1, 1, one) +#pragma param(height_scale, 2, 2, 1, zero) +#pragma param(height_offset, 2, 3, 1, zero) + +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; + +#pragma texture(normalmap, 1, RGBA) +layout(binding = 1) uniform sampler2D normalmap; + +#pragma texture(specgloss, 2, RGBA) +layout(binding = 2) uniform sampler2D specgloss; + +#pragma texture(detailnormalmap, 3, RGBA) +layout(binding = 3) 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); + } + 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); + } +} + +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; + } +} diff --git a/shaders/ogl46/mat_invalid.frag b/shaders/ogl46/mat_invalid.frag new file mode 100644 index 00000000..d4df13c5 --- /dev/null +++ b/shaders/ogl46/mat_invalid.frag @@ -0,0 +1,24 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +#include "common.glsl" +#include "tonemapping.glsl" + +layout(location = 0) out vec4 out_color; +layout(location = 1) out vec4 out_motion; + +void main() +{ + vec4 color = vec4(1.0, 0.0, 1.0, 1.0); + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } + if (MOTIONBLUR_ENABLED) + { + out_motion = vec4(0.0f); + } +} diff --git a/shaders/mat_normalmap.frag b/shaders/ogl46/mat_normalmap.frag similarity index 54% rename from shaders/mat_normalmap.frag rename to shaders/ogl46/mat_normalmap.frag index 182d67da..59f23277 100644 --- a/shaders/mat_normalmap.frag +++ b/shaders/ogl46/mat_normalmap.frag @@ -1,34 +1,34 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; -in mat3 f_tbn; +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +layout(location = 8) in mat3 f_tbn; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include +#include "common.glsl" 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) +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, one) +#pragma param(glossiness, 1, 3, 1, glossiness) -#texture (diffuse, 0, sRGB_A) -uniform sampler2D diffuse; +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; -#texture (normalmap, 1, RGBA) -uniform sampler2D normalmap; +#pragma texture(normalmap, 1, RGBA) +layout(binding = 1) uniform sampler2D normalmap; #define NORMALMAP -#include -#include -#include +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" void main() { @@ -53,17 +53,19 @@ void main() 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 + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } + 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 } diff --git a/shaders/mat_normalmap_specgloss.frag b/shaders/ogl46/mat_normalmap_specgloss.frag similarity index 54% rename from shaders/mat_normalmap_specgloss.frag rename to shaders/ogl46/mat_normalmap_specgloss.frag index a4b18462..ff8dd40e 100644 --- a/shaders/mat_normalmap_specgloss.frag +++ b/shaders/ogl46/mat_normalmap_specgloss.frag @@ -1,38 +1,38 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; -in mat3 f_tbn; +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +layout(location = 8) in mat3 f_tbn; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include +#include "common.glsl" 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) +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, one) +#pragma param(glossiness, 1, 3, 1, glossiness) -#texture (diffuse, 0, sRGB_A) -uniform sampler2D diffuse; +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; -#texture (normalmap, 1, RGBA) -uniform sampler2D normalmap; +#pragma texture(normalmap, 1, RGBA) +layout(binding = 1) uniform sampler2D normalmap; -#texture (specgloss, 2, RGBA) -uniform sampler2D specgloss; +#pragma texture(specgloss, 2, RGBA) +layout(binding = 2) uniform sampler2D specgloss; #define NORMALMAP -#include -#include -#include +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" void main() { @@ -58,17 +58,19 @@ void main() 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 + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } + 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 } diff --git a/shaders/ogl46/mat_parallax.frag b/shaders/ogl46/mat_parallax.frag new file mode 100644 index 00000000..47466ce6 --- /dev/null +++ b/shaders/ogl46/mat_parallax.frag @@ -0,0 +1,132 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +layout(location = 8) in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; +layout(location = 6) in vec3 TangentFragPos; + +#include "common.glsl" + +layout(location = 0) out vec4 out_color; +layout(location = 1) out vec4 out_motion; + +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, one) +#pragma param(glossiness, 1, 3, 1, glossiness) +#pragma param(height_scale, 2, 1, 1, zero) +#pragma param(height_offset, 2, 2, 1, zero) + +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; + +#pragma texture(normalmap, 1, RGBA) +layout(binding = 1) 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); + } + 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); + } +} + +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; + } +} diff --git a/shaders/ogl46/mat_parallax_specgloss.frag b/shaders/ogl46/mat_parallax_specgloss.frag new file mode 100644 index 00000000..01b3c310 --- /dev/null +++ b/shaders/ogl46/mat_parallax_specgloss.frag @@ -0,0 +1,137 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +layout(location = 8) in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; +layout(location = 6) in vec3 TangentFragPos; + +#include "common.glsl" + +layout(location = 0) out vec4 out_color; +layout(location = 1) out vec4 out_motion; + +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, one) +#pragma param(glossiness, 1, 3, 1, glossiness) +#pragma param(height_scale, 2, 1, 1, zero) +#pragma param(height_offset, 2, 2, 1, zero) + +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; + +#pragma texture(normalmap, 1, RGBA) +layout(binding = 1) uniform sampler2D normalmap; + +#pragma texture(specgloss, 2, RGBA) +layout(binding = 2) 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); + } + 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); + } +} + +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; + } +} diff --git a/shaders/mat_rain_windscreen.frag b/shaders/ogl46/mat_rain_windscreen.frag similarity index 86% rename from shaders/mat_rain_windscreen.frag rename to shaders/ogl46/mat_rain_windscreen.frag index c8dc657b..5babaab4 100644 --- a/shaders/mat_rain_windscreen.frag +++ b/shaders/ogl46/mat_rain_windscreen.frag @@ -1,39 +1,39 @@ +#version 460 core +#define STAGE_FRAGMENT 1 // Windshield rain effects // by @lcddisplay; wiper movement @MichauSto -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; -#include +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +#include "common.glsl" layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED layout(location = 1) out vec4 out_motion; -#endif -#texture(diffuse, 0, sRGB_A) -#texture(raindropsatlas, 1, sRGB_A) -#texture(wipermask, 2, sRGB_A) -#param (color, 0, 0, 4, diffuse) -#param (diffuse, 1, 0, 1, diffuse) -#param (specular, 1, 1, 1, specular) -#param (reflection, 1, 2, 1, zero) -#param (glossiness, 1, 3, 1, glossiness) -#param (raindrop_grid_size, 2, 0, 1, one) +#pragma texture(diffuse, 0, sRGB_A) +#pragma texture(raindropsatlas, 1, sRGB_A) +#pragma texture(wipermask, 2, sRGB_A) +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, zero) +#pragma param(glossiness, 1, 3, 1, glossiness) +#pragma param(raindrop_grid_size, 2, 0, 1, one) -#include -#include -#include -#include +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" +#include "random.glsl" -uniform sampler2D diffuse; -uniform sampler2D raindropsatlas; -uniform sampler2D wipermask; +layout(binding = 0) uniform sampler2D diffuse; +layout(binding = 1) uniform sampler2D raindropsatlas; +layout(binding = 2) uniform sampler2D wipermask; -uniform float specular_intensity = 1.0; -uniform float wobble_strength = 0.002; -uniform float wobble_speed = 30.0; +const float specular_intensity = 1.0; +const float wobble_strength = 0.002; +const float wobble_speed = 30.0; vec4 getDropTex(float choice, vec2 uv) { vec2 offset; @@ -167,9 +167,10 @@ void main() { out_color.a = mix(out_color.a, 1., color.a); } -#if MOTIONBLUR_ENABLED - out_motion = vec4(0.0); -#endif + if (MOTIONBLUR_ENABLED) + { + out_motion = vec4(0.0); + } } float GetMixFactor(in vec2 co, out float side) { diff --git a/shaders/ogl46/mat_reflmap.frag b/shaders/ogl46/mat_reflmap.frag new file mode 100644 index 00000000..69b37cec --- /dev/null +++ b/shaders/ogl46/mat_reflmap.frag @@ -0,0 +1,3 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +#include "mat_reflmap.glsl" diff --git a/shaders/mat_reflmap.frag b/shaders/ogl46/mat_reflmap.glsl similarity index 53% rename from shaders/mat_reflmap.frag rename to shaders/ogl46/mat_reflmap.glsl index 7e09f18a..ec8218af 100644 --- a/shaders/mat_reflmap.frag +++ b/shaders/ogl46/mat_reflmap.glsl @@ -1,32 +1,30 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include +#include "common.glsl" 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) +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, one) +#pragma param(glossiness, 1, 3, 1, glossiness) -#texture (diffuse, 0, sRGB_A) -uniform sampler2D diffuse; +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; -#texture (reflmap, 1, RGBA) -uniform sampler2D reflmap; +#pragma texture(reflmap, 1, RGBA) +layout(binding = 1) uniform sampler2D reflmap; -#include -#include -#include +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" void main() { @@ -51,17 +49,19 @@ void main() 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 + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } + 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 } diff --git a/shaders/mat_reflmap_specgloss.frag b/shaders/ogl46/mat_reflmap_specgloss.frag similarity index 51% rename from shaders/mat_reflmap_specgloss.frag rename to shaders/ogl46/mat_reflmap_specgloss.frag index 4c89083c..24d9b5d0 100644 --- a/shaders/mat_reflmap_specgloss.frag +++ b/shaders/ogl46/mat_reflmap_specgloss.frag @@ -1,36 +1,36 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include +#include "common.glsl" 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) +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, one) +#pragma param(glossiness, 1, 3, 1, glossiness) -#texture (diffuse, 0, sRGB_A) -uniform sampler2D diffuse; +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; -#texture (reflmap, 1, RGBA) -uniform sampler2D reflmap; +#pragma texture(reflmap, 1, RGBA) +layout(binding = 1) uniform sampler2D reflmap; -#texture (specgloss, 2, RGBA) -uniform sampler2D specgloss; +#pragma texture(specgloss, 2, RGBA) +layout(binding = 2) uniform sampler2D specgloss; -#include -#include -#include +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" void main() { @@ -56,17 +56,19 @@ void main() 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 + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } + 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 } diff --git a/shaders/mat_shadowlessnormalmap.frag b/shaders/ogl46/mat_shadowlessnormalmap.frag similarity index 54% rename from shaders/mat_shadowlessnormalmap.frag rename to shaders/ogl46/mat_shadowlessnormalmap.frag index 1a872527..dc030432 100644 --- a/shaders/mat_shadowlessnormalmap.frag +++ b/shaders/ogl46/mat_shadowlessnormalmap.frag @@ -1,34 +1,34 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; -in mat3 f_tbn; +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +layout(location = 8) in mat3 f_tbn; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include +#include "common.glsl" 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, zero) -#param (glossiness, 1, 3, 1, glossiness) +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, zero) +#pragma param(glossiness, 1, 3, 1, glossiness) -#texture (diffuse, 0, sRGB_A) -uniform sampler2D diffuse; +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; -#texture (normalmap, 1, RGBA) -uniform sampler2D normalmap; +#pragma texture(normalmap, 1, RGBA) +layout(binding = 1) uniform sampler2D normalmap; #define NORMALMAP -#include -#include -#include +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" void main() { @@ -52,17 +52,19 @@ void main() fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, 1.0); 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 + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } + 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 } diff --git a/shaders/mat_shadowlessnormalmap_specgloss.frag b/shaders/ogl46/mat_shadowlessnormalmap_specgloss.frag similarity index 53% rename from shaders/mat_shadowlessnormalmap_specgloss.frag rename to shaders/ogl46/mat_shadowlessnormalmap_specgloss.frag index adc473ff..8b1b999c 100644 --- a/shaders/mat_shadowlessnormalmap_specgloss.frag +++ b/shaders/ogl46/mat_shadowlessnormalmap_specgloss.frag @@ -1,38 +1,38 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; -in mat3 f_tbn; +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +layout(location = 8) in mat3 f_tbn; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include +#include "common.glsl" 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, zero) -#param (glossiness, 1, 3, 1, glossiness) +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, zero) +#pragma param(glossiness, 1, 3, 1, glossiness) -#texture (diffuse, 0, sRGB_A) -uniform sampler2D diffuse; +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; -#texture (normalmap, 1, RGBA) -uniform sampler2D normalmap; +#pragma texture(normalmap, 1, RGBA) +layout(binding = 1) uniform sampler2D normalmap; -#texture (specgloss, 2, RGBA) -uniform sampler2D specgloss; +#pragma texture(specgloss, 2, RGBA) +layout(binding = 2) uniform sampler2D specgloss; #define NORMALMAP -#include -#include -#include +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" void main() { @@ -57,17 +57,19 @@ void main() fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, 1.0); 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 + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } + 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 } diff --git a/shaders/mat_stars.frag b/shaders/ogl46/mat_stars.frag similarity index 53% rename from shaders/mat_stars.frag rename to shaders/ogl46/mat_stars.frag index aabc3ef3..f022ad8f 100644 --- a/shaders/mat_stars.frag +++ b/shaders/ogl46/mat_stars.frag @@ -1,12 +1,12 @@ -#include -#include +#version 460 core +#define STAGE_FRAGMENT 1 +#include "common.glsl" +#include "tonemapping.glsl" -flat in vec3 f_normal_raw; +layout(location = 1) flat in vec3 f_normal_raw; layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED layout(location = 1) out vec4 out_motion; -#endif void main() { @@ -18,12 +18,16 @@ void main() // color data space is shared with normals, ugh vec4 color = vec4(pow(f_normal_raw.rgb, vec3(2.2)), 1.0f); -#if POSTFX_ENABLED - out_color = color; -#else - out_color = tonemap(color); -#endif -#if MOTIONBLUR_ENABLED - out_motion = vec4(0.0f); -#endif + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } + if (MOTIONBLUR_ENABLED) + { + out_motion = vec4(0.0f); + } } diff --git a/shaders/mat_sunlessnormalmap.frag b/shaders/ogl46/mat_sunlessnormalmap.frag similarity index 54% rename from shaders/mat_sunlessnormalmap.frag rename to shaders/ogl46/mat_sunlessnormalmap.frag index db3c1386..7f1f8dc2 100644 --- a/shaders/mat_sunlessnormalmap.frag +++ b/shaders/ogl46/mat_sunlessnormalmap.frag @@ -1,68 +1,70 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; -in mat3 f_tbn; - -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; - -#include - -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, zero) -#param (glossiness, 1, 3, 1, glossiness) - -#texture (diffuse, 0, sRGB_A) -uniform sampler2D diffuse; - -#texture (normalmap, 1, RGBA) -uniform sampler2D normalmap; - -#define NORMALMAP -#include -#include -#include - -void main() -{ - vec4 tex_color = texture(diffuse, f_coord); - - 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).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).a; - float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5; - legacy_gloss(); - - fragcolor = apply_lights_sunless(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 -} +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +layout(location = 8) in mat3 f_tbn; + +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; + +#include "common.glsl" + +layout(location = 0) out vec4 out_color; +layout(location = 1) out vec4 out_motion; + +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, zero) +#pragma param(glossiness, 1, 3, 1, glossiness) + +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; + +#pragma texture(normalmap, 1, RGBA) +layout(binding = 1) uniform sampler2D normalmap; + +#define NORMALMAP +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" + +void main() +{ + vec4 tex_color = texture(diffuse, f_coord); + + 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).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).a; + float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5; + legacy_gloss(); + + fragcolor = apply_lights_sunless(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); + } + 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); + } +} diff --git a/shaders/mat_sunlessnormalmap_specgloss.frag b/shaders/ogl46/mat_sunlessnormalmap_specgloss.frag similarity index 54% rename from shaders/mat_sunlessnormalmap_specgloss.frag rename to shaders/ogl46/mat_sunlessnormalmap_specgloss.frag index 93e2c244..e5bbc8e8 100644 --- a/shaders/mat_sunlessnormalmap_specgloss.frag +++ b/shaders/ogl46/mat_sunlessnormalmap_specgloss.frag @@ -1,73 +1,75 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; -in mat3 f_tbn; - -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; - -#include - -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, zero) -#param (glossiness, 1, 3, 1, glossiness) - -#texture (diffuse, 0, sRGB_A) -uniform sampler2D diffuse; - -#texture (normalmap, 1, RGBA) -uniform sampler2D normalmap; - -#texture (specgloss, 2, RGBA) -uniform sampler2D specgloss; - - -#define NORMALMAP -#include -#include -#include - -void main() -{ - vec4 tex_color = texture(diffuse, f_coord); - - 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).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).a; - float specularity = texture(specgloss, f_coord).r; - glossiness = texture(specgloss, f_coord).g * abs(param[1].w); - metalic = texture(specgloss, f_coord).b; - - fragcolor = apply_lights_sunless(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 -} +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +layout(location = 8) in mat3 f_tbn; + +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; + +#include "common.glsl" + +layout(location = 0) out vec4 out_color; +layout(location = 1) out vec4 out_motion; + +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, zero) +#pragma param(glossiness, 1, 3, 1, glossiness) + +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; + +#pragma texture(normalmap, 1, RGBA) +layout(binding = 1) uniform sampler2D normalmap; + +#pragma texture(specgloss, 2, RGBA) +layout(binding = 2) uniform sampler2D specgloss; + + +#define NORMALMAP +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" + +void main() +{ + vec4 tex_color = texture(diffuse, f_coord); + + 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).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).a; + float specularity = texture(specgloss, f_coord).r; + glossiness = texture(specgloss, f_coord).g * abs(param[1].w); + metalic = texture(specgloss, f_coord).b; + + fragcolor = apply_lights_sunless(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); + } + 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); + } +} diff --git a/shaders/ogl46/mat_terrain.frag b/shaders/ogl46/mat_terrain.frag new file mode 100644 index 00000000..7739b454 --- /dev/null +++ b/shaders/ogl46/mat_terrain.frag @@ -0,0 +1,3 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +#include "mat_terrain.glsl" diff --git a/shaders/mat_terrain.frag b/shaders/ogl46/mat_terrain.glsl similarity index 76% rename from shaders/mat_terrain.frag rename to shaders/ogl46/mat_terrain.glsl index 0320c626..f47fc089 100644 --- a/shaders/mat_terrain.frag +++ b/shaders/ogl46/mat_terrain.glsl @@ -1,17 +1,15 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; -in mat3 f_tbn; +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +layout(location = 8) in mat3 f_tbn; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include +#include "common.glsl" layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED layout(location = 1) out vec4 out_motion; -#endif // heightmap terrain: up to 8 materials (layers) painted over a chunk, blended by their weights. // the textures of all layers of the terrain are kept in texture arrays; the table of the chunk tells which layers it has: @@ -19,35 +17,35 @@ layout(location = 1) out vec4 out_motion; // row 1, per layer: reflection, cosine and sine of the angle the textures are turned by // f_coord holds metres from the corner of the pack of chunks, along x and -z -#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) +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, one) +#pragma param(glossiness, 1, 3, 1, glossiness) // corner of the chunk (x, z) in texture coordinates, 1 / size of the chunk, paint samples along a side less one -#param (placement, 2, 0, 4, zero) +#pragma param(placement, 2, 0, 4, zero) -#texture (opaque, 0, RGBA) -uniform sampler2D opaque; -#texture (weights0, 1, RGBA) -uniform sampler2D weights0; -#texture (weights1, 2, RGBA) -uniform sampler2D weights1; -#texture (layerdiffuse, 3, sRGB_A) -uniform highp sampler2DArray layerdiffuse; -#texture (layernormal, 4, RGBA) -uniform highp sampler2DArray layernormal; -#texture (layerspecgloss, 5, RGBA) -uniform highp sampler2DArray layerspecgloss; -#texture (layertable, 6, RGBA) -uniform highp sampler2D layertable; +#pragma texture(opaque, 0, RGBA) +layout(binding = 0) uniform sampler2D opaque; +#pragma texture(weights0, 1, RGBA) +layout(binding = 1) uniform sampler2D weights0; +#pragma texture(weights1, 2, RGBA) +layout(binding = 2) uniform sampler2D weights1; +#pragma texture(layerdiffuse, 3, sRGB_A) +layout(binding = 3) uniform highp sampler2DArray layerdiffuse; +#pragma texture(layernormal, 4, RGBA) +layout(binding = 4) uniform highp sampler2DArray layernormal; +#pragma texture(layerspecgloss, 5, RGBA) +layout(binding = 5) uniform highp sampler2DArray layerspecgloss; +#pragma texture(layertable, 6, RGBA) +layout(binding = 6) uniform highp sampler2D layertable; #define NORMALMAP // param[2] holds the placement of the chunk, not the weathering #define NO_WEATHERING -#include -#include -#include +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" void main() { @@ -137,17 +135,19 @@ void main() discard; #endif vec4 color = vec4(apply_fog(fragcolor), alpha_mult); -#if POSTFX_ENABLED - out_color = color; -#else - out_color = tonemap(color); -#endif -#if MOTIONBLUR_ENABLED + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } + 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 } diff --git a/shaders/mat_terrain_holes.frag b/shaders/ogl46/mat_terrain_holes.frag similarity index 72% rename from shaders/mat_terrain_holes.frag rename to shaders/ogl46/mat_terrain_holes.frag index 4848b1df..640cd03e 100644 --- a/shaders/mat_terrain_holes.frag +++ b/shaders/ogl46/mat_terrain_holes.frag @@ -1,9 +1,11 @@ +#version 460 core +#define STAGE_FRAGMENT 1 // heightmap terrain with holes cut in it, for the portals of tunnels and the like (opengl46_terrain_materials): the cells cut // out entirely are left out of the mesh, the rest of the openings is cut here. the mask of the holes covers the chunk, 255 // where the ground is cut; the fragments where the value led between its samples is over the half are discarded #define TERRAIN_HOLES 1 -#texture (holes, 7, R) -uniform sampler2D holes; +#pragma texture(holes, 7, R) +layout(binding = 7) uniform sampler2D holes; -#include +#include "mat_terrain.glsl" diff --git a/shaders/ogl46/mat_trackblend.frag b/shaders/ogl46/mat_trackblend.frag new file mode 100644 index 00000000..799c98b5 --- /dev/null +++ b/shaders/ogl46/mat_trackblend.frag @@ -0,0 +1,12 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +// the rails and the ballast of a track faded into those of the tracks joined at its ends, see trackblend.glsl +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; +#pragma texture(diffuse1, 1, sRGB_A) +layout(binding = 1) uniform sampler2D diffuse1; +#pragma texture(diffuse2, 2, sRGB_A) +layout(binding = 2) uniform sampler2D diffuse2; +#pragma texture(diffuse3, 3, sRGB_A) +layout(binding = 3) uniform sampler2D diffuse3; +#include "trackblend.glsl" diff --git a/shaders/ogl46/mat_trackblend_normalmap.frag b/shaders/ogl46/mat_trackblend_normalmap.frag new file mode 100644 index 00000000..ca46d00c --- /dev/null +++ b/shaders/ogl46/mat_trackblend_normalmap.frag @@ -0,0 +1,21 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +// 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 +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; +#pragma texture(diffuse1, 1, sRGB_A) +layout(binding = 1) uniform sampler2D diffuse1; +#pragma texture(diffuse2, 2, sRGB_A) +layout(binding = 2) uniform sampler2D diffuse2; +#pragma texture(diffuse3, 3, sRGB_A) +layout(binding = 3) uniform sampler2D diffuse3; +#pragma texture(normalmap, 4, RGBA) +layout(binding = 4) uniform sampler2D normalmap; +#pragma texture(normalmap1, 5, RGBA) +layout(binding = 5) uniform sampler2D normalmap1; +#pragma texture(normalmap2, 6, RGBA) +layout(binding = 6) uniform sampler2D normalmap2; +#pragma texture(normalmap3, 7, RGBA) +layout(binding = 7) uniform sampler2D normalmap3; +#include "trackblend.glsl" diff --git a/shaders/ogl46/mat_trackblend_parallax.frag b/shaders/ogl46/mat_trackblend_parallax.frag new file mode 100644 index 00000000..9240b773 --- /dev/null +++ b/shaders/ogl46/mat_trackblend_parallax.frag @@ -0,0 +1,24 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +// 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 +#pragma param(height_scale, 2, 1, 1, zero) +#pragma param(height_offset, 2, 2, 1, zero) +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; +#pragma texture(diffuse1, 1, sRGB_A) +layout(binding = 1) uniform sampler2D diffuse1; +#pragma texture(diffuse2, 2, sRGB_A) +layout(binding = 2) uniform sampler2D diffuse2; +#pragma texture(diffuse3, 3, sRGB_A) +layout(binding = 3) uniform sampler2D diffuse3; +#pragma texture(normalmap, 4, RGBA) +layout(binding = 4) uniform sampler2D normalmap; +#pragma texture(normalmap1, 5, RGBA) +layout(binding = 5) uniform sampler2D normalmap1; +#pragma texture(normalmap2, 6, RGBA) +layout(binding = 6) uniform sampler2D normalmap2; +#pragma texture(normalmap3, 7, RGBA) +layout(binding = 7) uniform sampler2D normalmap3; +#include "trackblend.glsl" diff --git a/shaders/ogl46/mat_trackblend_specgloss.frag b/shaders/ogl46/mat_trackblend_specgloss.frag new file mode 100644 index 00000000..4e861acf --- /dev/null +++ b/shaders/ogl46/mat_trackblend_specgloss.frag @@ -0,0 +1,20 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +// 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 +#pragma texture(diffuse, 0, sRGB_A) +layout(binding = 0) uniform sampler2D diffuse; +#pragma texture(diffuse1, 1, sRGB_A) +layout(binding = 1) uniform sampler2D diffuse1; +#pragma texture(diffuse2, 2, sRGB_A) +layout(binding = 2) uniform sampler2D diffuse2; +#pragma texture(normalmap, 3, RGBA) +layout(binding = 3) uniform sampler2D normalmap; +#pragma texture(normalmap1, 4, RGBA) +layout(binding = 4) uniform sampler2D normalmap1; +#pragma texture(normalmap2, 5, RGBA) +layout(binding = 5) uniform sampler2D normalmap2; +#pragma texture(specgloss, 6, RGBA) +layout(binding = 6) uniform sampler2D specgloss; +#include "trackblend.glsl" diff --git a/shaders/mat_water.frag b/shaders/ogl46/mat_water.frag similarity index 66% rename from shaders/mat_water.frag rename to shaders/ogl46/mat_water.frag index b1c34179..1f20d5d6 100644 --- a/shaders/mat_water.frag +++ b/shaders/ogl46/mat_water.frag @@ -1,42 +1,42 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; -in mat3 f_tbn; +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +layout(location = 8) in mat3 f_tbn; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include +#include "common.glsl" 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 (wave_strength, 2, 1, 1, one) -#param (wave_speed, 2, 2, 1, one) +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, one) +#pragma param(glossiness, 1, 3, 1, glossiness) +#pragma param(wave_strength, 2, 1, 1, one) +#pragma param(wave_speed, 2, 2, 1, one) -#texture (normalmap, 0, RGBA) -uniform sampler2D normalmap; +#pragma texture(normalmap, 0, RGBA) +layout(binding = 0) uniform sampler2D normalmap; -#texture (dudvmap, 1, RGB) -uniform sampler2D dudvmap; +#pragma texture(dudvmap, 1, RGB) +layout(binding = 1) uniform sampler2D dudvmap; -#texture (diffuse, 2, sRGB_A) -uniform sampler2D diffuse; +#pragma texture(diffuse, 2, sRGB_A) +layout(binding = 2) uniform sampler2D diffuse; //wave distortion variables float move_factor = 0.0; #define WATER -#include -#include -#include +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" void main() { @@ -75,17 +75,19 @@ void main() float fresnel_inv = ((fresnel - 1.0 ) * -1.0 ); vec4 color = vec4(apply_fog(fragcolor), clamp((fresnel_inv + param[0].a), 0.0, 1.0)); -#if POSTFX_ENABLED - out_color = color; -#else - out_color = tonemap(color); -#endif -#if MOTIONBLUR_ENABLED + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } + 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 } diff --git a/shaders/mat_water_specgloss.frag b/shaders/ogl46/mat_water_specgloss.frag similarity index 65% rename from shaders/mat_water_specgloss.frag rename to shaders/ogl46/mat_water_specgloss.frag index c11e5eb9..c73ef2be 100644 --- a/shaders/mat_water_specgloss.frag +++ b/shaders/ogl46/mat_water_specgloss.frag @@ -1,46 +1,46 @@ -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; -in mat3 f_tbn; +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +layout(location = 8) in mat3 f_tbn; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include +#include "common.glsl" 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 (wave_strength, 2, 1, 1, one) -#param (wave_speed, 2, 2, 1, one) +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, one) +#pragma param(glossiness, 1, 3, 1, glossiness) +#pragma param(wave_strength, 2, 1, 1, one) +#pragma param(wave_speed, 2, 2, 1, one) -#texture (normalmap, 0, RGBA) -uniform sampler2D normalmap; +#pragma texture(normalmap, 0, RGBA) +layout(binding = 0) uniform sampler2D normalmap; -#texture (dudvmap, 1, RGB) -uniform sampler2D dudvmap; +#pragma texture(dudvmap, 1, RGB) +layout(binding = 1) uniform sampler2D dudvmap; -#texture (diffuse, 2, sRGB_A) -uniform sampler2D diffuse; +#pragma texture(diffuse, 2, sRGB_A) +layout(binding = 2) uniform sampler2D diffuse; -#texture (specgloss, 3, RGBA) -uniform sampler2D specgloss; +#pragma texture(specgloss, 3, RGBA) +layout(binding = 3) uniform sampler2D specgloss; //wave distortion variables float move_factor = 0.0; #define WATER -#include -#include -#include +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" void main() { @@ -80,17 +80,19 @@ void main() float fresnel_inv = ((fresnel - 1.0 ) * -1.0 ); vec4 color = vec4(apply_fog(fragcolor), clamp((fresnel_inv + param[0].a), 0.0, 1.0)); -#if POSTFX_ENABLED - out_color = color; -#else - out_color = tonemap(color); -#endif -#if MOTIONBLUR_ENABLED + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } + 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 } diff --git a/shaders/particle_common.glsl b/shaders/ogl46/particle_common.glsl similarity index 100% rename from shaders/particle_common.glsl rename to shaders/ogl46/particle_common.glsl diff --git a/shaders/particle_light.glsl b/shaders/ogl46/particle_light.glsl similarity index 84% rename from shaders/particle_light.glsl rename to shaders/ogl46/particle_light.glsl index b647e9c9..42e4db24 100644 --- a/shaders/particle_light.glsl +++ b/shaders/ogl46/particle_light.glsl @@ -8,10 +8,8 @@ #ifndef PARTICLE_LIGHT_GLSL #define PARTICLE_LIGHT_GLSL -#if SHADOWMAP_ENABLED -uniform sampler2DArrayShadow shadowmap; -#endif -uniform sampler2D headlightmap; +layout(binding = SHADOW_TEX) uniform sampler2DArrayShadow shadowmap; +layout(binding = HEADLIGHT_TEX) uniform sampler2D headlightmap; layout(binding = HEADLIGHT_SHADOW_TEX) uniform sampler2DArrayShadow headlight_shadowmap; // AMBIENT_SCALE and SUN_DIFFUSE_SCALE of light_common.glsl @@ -28,31 +26,34 @@ float particle_phase(float cosangle, float g) // share of the sunlight the shadow map holds back at a point of the view space, one comparison (as fog_inject.comp) float particle_sun_shadow(vec3 position) { -#if SHADOWMAP_ENABLED - float span_distance = dot(position, position); - uint cascade; - for (cascade = 0U; cascade < MAX_CASCADES; cascade++) - if (span_distance <= cascade_end[cascade]) - break; - for (; cascade < MAX_CASCADES; cascade++) + if (SHADOWMAP_ENABLED) { - vec4 lightpos = lightview[cascade] * vec4(position, 1.0); - vec3 coords = lightpos.xyz / lightpos.w; - if (all(greaterThanEqual(coords.xy, vec2(0.0))) && all(lessThanEqual(coords.xy, vec2(1.0)))) + float span_distance = dot(position, position); + uint cascade; + for (cascade = 0U; cascade < MAX_CASCADES; cascade++) + if (span_distance <= cascade_end[cascade]) + break; + for (; cascade < MAX_CASCADES; cascade++) { - if (coords.z < 0.0) + vec4 lightpos = lightview[cascade] * vec4(position, 1.0); + vec3 coords = lightpos.xyz / lightpos.w; + if (all(greaterThanEqual(coords.xy, vec2(0.0))) && all(lessThanEqual(coords.xy, vec2(1.0)))) + { + if (coords.z < 0.0) + return 0.0; + return texture(shadowmap, vec4(coords.xy, float(cascade), coords.z + 0.0001 * float(cascade + 1U))); + } + // outside the map: lit, unless the cascades are fitted to the depth of the view (gfx.shadow.sdsm), whose maps cover only + // the area of the surfaces seen; then the first further cascade whose map holds the point + if (shadow_fit.x <= 0.0) return 0.0; - return texture(shadowmap, vec4(coords.xy, float(cascade), coords.z + 0.0001 * float(cascade + 1U))); } - // outside the map: lit, unless the cascades are fitted to the depth of the view (gfx.shadow.sdsm), whose maps cover only - // the area of the surfaces seen; then the first further cascade whose map holds the point - if (shadow_fit.x <= 0.0) - return 0.0; + return 0.0; + } + else + { + return 0.0; } - return 0.0; -#else - return 0.0; -#endif } // light of a lamp or of the beam of headlights reaching a point of the view space (as fog_inject.comp; the beams with their diff --git a/shaders/particle_soft.glsl b/shaders/ogl46/particle_soft.glsl similarity index 100% rename from shaders/particle_soft.glsl rename to shaders/ogl46/particle_soft.glsl diff --git a/shaders/particles_precipitation.comp b/shaders/ogl46/particles_precipitation.comp similarity index 95% rename from shaders/particles_precipitation.comp rename to shaders/ogl46/particles_precipitation.comp index a6439653..4973cd53 100644 --- a/shaders/particles_precipitation.comp +++ b/shaders/ogl46/particles_precipitation.comp @@ -1,13 +1,15 @@ +#version 460 core +#define STAGE_COMPUTE 1 // rain and snow of the gpu (opengl46_gpu_particles), in every colour pass which draws them: a thread per drop or flake. the // particles fill three boxes around the camera, a small dense one and two larger ones, each repeated along the world axes: // a particle keeps its place in the box, moved by how far the wind took them all and how far it fell, so nothing is kept // from frame to frame. what the camera sees is lit (particle_light.glsl) and drawn as a streak from where it is to where // it was a moment ago, seen from the moving camera (particles_precipitation.vert) -#include -#include -#include -#include +#include "common.glsl" +#include "weather_common.glsl" +#include "particle_light.glsl" +#include "particle_common.glsl" layout(local_size_x = PARTICLE_GROUP_SIZE) in; diff --git a/shaders/particles_precipitation.frag b/shaders/ogl46/particles_precipitation.frag similarity index 65% rename from shaders/particles_precipitation.frag rename to shaders/ogl46/particles_precipitation.frag index a8c478a8..4a65260d 100644 --- a/shaders/particles_precipitation.frag +++ b/shaders/ogl46/particles_precipitation.frag @@ -1,23 +1,23 @@ +#version 460 core +#define STAGE_FRAGMENT 1 // rain and snow of the gpu (opengl46_gpu_particles, particles_precipitation.vert): a streak rounded at its ends, fainter towards // its tail, in the colour the compute pass lit it with, faded in front of the scene, fogged -in vec4 f_pos; -in vec4 f_color; -in vec4 f_streak; -in float f_soft; +layout(location = 3) in vec4 f_pos; +layout(location = 13) in vec4 f_color; +layout(location = 17) in vec4 f_streak; +layout(location = 16) in float f_soft; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include -#include -#include -#include +#include "common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" +#include "particle_soft.glsl" layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED layout(location = 1) out vec4 out_motion; -#endif void main() { @@ -30,17 +30,19 @@ void main() if (alpha <= 0.0) discard; vec4 color = vec4(apply_fog(f_color.rgb), alpha); -#if POSTFX_ENABLED - out_color = color; -#else - out_color = tonemap(color); -#endif -#if MOTIONBLUR_ENABLED + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } + 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, alpha * alpha_mult); } -#endif } diff --git a/shaders/particles_precipitation.vert b/shaders/ogl46/particles_precipitation.vert similarity index 86% rename from shaders/particles_precipitation.vert rename to shaders/ogl46/particles_precipitation.vert index a4cbf144..2ed2d0df 100644 --- a/shaders/particles_precipitation.vert +++ b/shaders/ogl46/particles_precipitation.vert @@ -1,3 +1,5 @@ +#version 460 core +#define STAGE_VERTEX 1 // rain and snow of the gpu (opengl46_gpu_particles): a streak per drop or flake the compute pass added to the draw, from where // it is to where it was a moment ago as the moving camera saw it, turned to face the camera, rounded at its ends. a particle // thinner than a pixel and a half is drawn that wide and as much fainter, so the distant ones don't flicker @@ -6,16 +8,16 @@ layout(location = 0) in vec4 v_place; // xyz: the head, metres from the camera ( layout(location = 1) in vec4 v_shape; // xyz: from the head to the tail layout(location = 2) in vec4 v_color; // rgb: lit colour, a: opacity -out vec4 f_pos; -out vec4 f_color; +layout(location = 3) out vec4 f_pos; +layout(location = 13) out vec4 f_color; // x: across the streak, y: along it from the head, metres; z: its radius, w: its length -out vec4 f_streak; -out float f_soft; +layout(location = 17) out vec4 f_streak; +layout(location = 16) out float f_soft; -out vec4 f_clip_pos; -out vec4 f_clip_future_pos; +layout(location = 4) out vec4 f_clip_pos; +layout(location = 5) out vec4 f_clip_future_pos; -#include +#include "common.glsl" // x: metres it fades out over in front of the scene, 0: none layout(location = 0) uniform vec4 particle_soft; diff --git a/shaders/ogl46/particles_smoke.frag b/shaders/ogl46/particles_smoke.frag new file mode 100644 index 00000000..630d7130 --- /dev/null +++ b/shaders/ogl46/particles_smoke.frag @@ -0,0 +1,44 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +// the smoke of the gpu (opengl46_gpu_particles, particles_smoke.vert): the texture of the smoke in the colour the compute pass +// lit the particle with, faded in front of the scene, fogged + +layout(location = 13) in vec4 f_color; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; +layout(location = 16) in float f_soft; + +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; + +#pragma texture(tex1, 0, sRGB_A) +layout(binding = 0) uniform sampler2D tex1; + +#include "common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" +#include "particle_soft.glsl" + +layout(location = 0) out vec4 out_color; +layout(location = 1) out vec4 out_motion; + +void main() +{ + vec4 tex_color = texture(tex1, f_coord) * f_color; + tex_color.a *= particle_soft_fade(f_soft, -f_pos.z); + if (POSTFX_ENABLED) + { + out_color = vec4(apply_fog(tex_color.rgb), tex_color.a); + } + else + { + out_color = tonemap(vec4(apply_fog(tex_color.rgb), tex_color.a)); + } + 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, tex_color.a * alpha_mult); + } +} diff --git a/shaders/particles_smoke.vert b/shaders/ogl46/particles_smoke.vert similarity index 80% rename from shaders/particles_smoke.vert rename to shaders/ogl46/particles_smoke.vert index 77ec42e7..f0288e9c 100644 --- a/shaders/particles_smoke.vert +++ b/shaders/ogl46/particles_smoke.vert @@ -1,3 +1,5 @@ +#version 460 core +#define STAGE_VERTEX 1 // the smoke of the gpu (opengl46_gpu_particles): a billboard per particle the compute pass added to the draw, turned about the // axis of the view by the rotation of the particle (as opengl46_particles builds them on the cpu) @@ -5,16 +7,16 @@ layout(location = 0) in vec4 v_place; // xyz: metres from the camera (world axes layout(location = 1) in vec4 v_shape; // x: rotation, radians layout(location = 2) in vec4 v_color; // rgb: lit colour, a: opacity -out vec4 f_pos; -out vec4 f_color; -out vec2 f_coord; +layout(location = 3) out vec4 f_pos; +layout(location = 13) out vec4 f_color; +layout(location = 2) out vec2 f_coord; // metres in front of the scene the particle fades out over, see particle_soft.glsl -out float f_soft; +layout(location = 16) out float f_soft; -out vec4 f_clip_pos; -out vec4 f_clip_future_pos; +layout(location = 4) out vec4 f_clip_pos; +layout(location = 5) out vec4 f_clip_future_pos; -#include +#include "common.glsl" // x: share of the size of the particle it fades out over in front of the scene (gfx.particles.soft), 0: none layout(location = 0) uniform vec4 particle_soft; diff --git a/shaders/particles_smoke_prepare.comp b/shaders/ogl46/particles_smoke_prepare.comp similarity index 92% rename from shaders/particles_smoke_prepare.comp rename to shaders/ogl46/particles_smoke_prepare.comp index 1ed8ec8e..441525fc 100644 --- a/shaders/particles_smoke_prepare.comp +++ b/shaders/ogl46/particles_smoke_prepare.comp @@ -1,10 +1,12 @@ +#version 460 core +#define STAGE_COMPUTE 1 // the smoke of the gpu (opengl46_gpu_particles), in every colour pass which draws it: a thread per place of the storage. the // living particles the view sees are lit (particle_light.glsl) and added to the draw (particles_smoke.vert) -#include -#include -#include -#include +#include "common.glsl" +#include "weather_common.glsl" +#include "particle_light.glsl" +#include "particle_common.glsl" layout(local_size_x = PARTICLE_GROUP_SIZE) in; diff --git a/shaders/particles_smoke_simulate.comp b/shaders/ogl46/particles_smoke_simulate.comp similarity index 97% rename from shaders/particles_smoke_simulate.comp rename to shaders/ogl46/particles_smoke_simulate.comp index ebc64cd2..4c4e2d63 100644 --- a/shaders/particles_smoke_simulate.comp +++ b/shaders/ogl46/particles_smoke_simulate.comp @@ -1,10 +1,12 @@ +#version 460 core +#define STAGE_COMPUTE 1 // the smoke of the gpu (opengl46_gpu_particles), once per update of the simulation: a thread per place of the storage. the // particles the sources had born in the update take the places after the ones of the last update (each source keeps its // places as a ring, the oldest give way), spread along the way the emitter went since the update before; the living ones // change as smoke_source::update() changes them on the cpu -#include -#include +#include "common.glsl" +#include "particle_common.glsl" layout(local_size_x = PARTICLE_GROUP_SIZE) in; diff --git a/shaders/pick.frag b/shaders/ogl46/pick.frag similarity index 59% rename from shaders/pick.frag rename to shaders/ogl46/pick.frag index cd9abc4a..dd4b5eb4 100644 --- a/shaders/pick.frag +++ b/shaders/ogl46/pick.frag @@ -1,4 +1,6 @@ -#include +#version 460 core +#define STAGE_FRAGMENT 1 +#include "common.glsl" layout(location = 0) out vec4 out_color; diff --git a/shaders/pick_surface.frag b/shaders/ogl46/pick_surface.frag similarity index 50% rename from shaders/pick_surface.frag rename to shaders/ogl46/pick_surface.frag index 459d1ebf..5b527844 100644 --- a/shaders/pick_surface.frag +++ b/shaders/ogl46/pick_surface.frag @@ -1,7 +1,9 @@ -#include +#version 460 core +#define STAGE_FRAGMENT 1 +#include "common.glsl" layout(location = 0) out vec4 out_color; -in vec2 f_coord; +layout(location = 2) in vec2 f_coord; void main() { diff --git a/shaders/postfx_bloom_down.frag b/shaders/ogl46/postfx_bloom_down.frag similarity index 92% rename from shaders/postfx_bloom_down.frag rename to shaders/ogl46/postfx_bloom_down.frag index 21888efa..c792e002 100644 --- a/shaders/postfx_bloom_down.frag +++ b/shaders/ogl46/postfx_bloom_down.frag @@ -1,15 +1,17 @@ +#version 460 core +#define STAGE_FRAGMENT 1 // one step down the bloom chain: a 13 tap filter (Jimenez, "Next generation post processing in Call of Duty: Advanced // Warfare", 2014) from a level twice as large. the first step, from the image itself, weighs the groups of taps by their // brightness (Karis average) so single bright pixels do not flicker, and applies the threshold. // param[0].x: 1 for the first step, param[0].y: threshold (0: none), param[0].z: soft knee -in vec2 f_coords; +layout(location = 14) in vec2 f_coords; layout(location = 0) out vec4 out_color; -#texture (source_tex, 0, RGB) -uniform sampler2D source_tex; +#pragma texture(source_tex, 0, RGB) +layout(binding = 0) uniform sampler2D source_tex; -#include +#include "common.glsl" vec3 tap(vec2 uv) { diff --git a/shaders/postfx_bloom_up.frag b/shaders/ogl46/postfx_bloom_up.frag similarity index 78% rename from shaders/postfx_bloom_up.frag rename to shaders/ogl46/postfx_bloom_up.frag index 160cf52f..75017316 100644 --- a/shaders/postfx_bloom_up.frag +++ b/shaders/ogl46/postfx_bloom_up.frag @@ -1,14 +1,16 @@ +#version 460 core +#define STAGE_FRAGMENT 1 // one step up the bloom chain: the level below (half as large, with all the levels under it already added) blurred up with // a 3x3 tent filter and added to this level of the way down -in vec2 f_coords; +layout(location = 14) in vec2 f_coords; layout(location = 0) out vec4 out_color; -#texture (lower_tex, 0, RGB) -uniform sampler2D lower_tex; +#pragma texture(lower_tex, 0, RGB) +layout(binding = 0) uniform sampler2D lower_tex; -#texture (current_tex, 1, RGB) -uniform sampler2D current_tex; +#pragma texture(current_tex, 1, RGB) +layout(binding = 1) uniform sampler2D current_tex; void main() { diff --git a/shaders/postfx_chromaticaberration.frag b/shaders/ogl46/postfx_chromaticaberration.frag similarity index 85% rename from shaders/postfx_chromaticaberration.frag rename to shaders/ogl46/postfx_chromaticaberration.frag index 09fa7b00..7174edfb 100644 --- a/shaders/postfx_chromaticaberration.frag +++ b/shaders/ogl46/postfx_chromaticaberration.frag @@ -1,12 +1,14 @@ -in vec2 f_coords; +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 14) in vec2 f_coords; layout(location = 0) out vec4 out_color; -#texture (color_tex, 0, RGB) +#pragma texture(color_tex, 0, RGB) -uniform sampler2D iChannel0; +layout(binding = 0) uniform sampler2D iChannel0; -#include +#include "dither.glsl" vec2 barrelDistortion(vec2 f_coords, float amt) { vec2 cc = f_coords - 0.5; diff --git a/shaders/postfx_motionblur.frag b/shaders/ogl46/postfx_motionblur.frag similarity index 70% rename from shaders/postfx_motionblur.frag rename to shaders/ogl46/postfx_motionblur.frag index 146179ac..c5f51657 100644 --- a/shaders/postfx_motionblur.frag +++ b/shaders/ogl46/postfx_motionblur.frag @@ -1,16 +1,18 @@ -in vec2 f_coords; +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 14) in vec2 f_coords; layout(location = 0) out vec4 out_color; -#texture (color_tex, 0, RGB) -uniform sampler2D color_tex; +#pragma texture(color_tex, 0, RGB) +layout(binding = 0) uniform sampler2D color_tex; -#texture (velocity_tex, 1, RG) -uniform sampler2D velocity_tex; +#pragma texture(velocity_tex, 1, RG) +layout(binding = 1) uniform sampler2D velocity_tex; -#texture (depth_tex, 2, R) -uniform sampler2D depth_tex; +#pragma texture(depth_tex, 2, R) +layout(binding = 2) uniform sampler2D depth_tex; -#include +#include "common.glsl" void main() { diff --git a/shaders/postfx_ssao.frag b/shaders/ogl46/postfx_ssao.frag similarity index 88% rename from shaders/postfx_ssao.frag rename to shaders/ogl46/postfx_ssao.frag index 35a34841..c9eb02ef 100644 --- a/shaders/postfx_ssao.frag +++ b/shaders/ogl46/postfx_ssao.frag @@ -1,13 +1,15 @@ -in vec2 f_coords; +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 14) in vec2 f_coords; layout(location = 0) out vec4 out_color; -#texture (depth_tex, 0, R) -uniform sampler2D depth_tex; +#pragma texture(depth_tex, 0, R) +layout(binding = 0) uniform sampler2D depth_tex; -#texture (noise_tex, 1, RGB) -uniform sampler2D noise_tex; +#pragma texture(noise_tex, 1, RGB) +layout(binding = 1) uniform sampler2D noise_tex; -#include +#include "common.glsl" // the depth is reversed (glClipControl GL_ZERO_TO_ONE, 1 at the near plane, 0 at infinity), so it is the clip z itself vec3 view_pos_from_depth(vec2 uv, float depth) { diff --git a/shaders/postfx_ssao_apply.frag b/shaders/ogl46/postfx_ssao_apply.frag similarity index 96% rename from shaders/postfx_ssao_apply.frag rename to shaders/ogl46/postfx_ssao_apply.frag index 259ed526..164bf87e 100644 --- a/shaders/postfx_ssao_apply.frag +++ b/shaders/ogl46/postfx_ssao_apply.frag @@ -1,3 +1,5 @@ +#version 460 core +#define STAGE_FRAGMENT 1 // applies the ambient occlusion of the half size target to the image at full size, as a multiplier: drawn with the // blending dst * src over the image (opengl46_ao::apply). the four nearest pixels of the occlusion are weighed by how // close they are and by how close their distance is to that of the pixel, so the occlusion does not spill over the @@ -6,10 +8,10 @@ // params.y: strength, a power of the occlusion (1 = as computed); params.z: albedo assumed by the multiple bounce term. // the fog in front of a surface hides its occlusion as it hides the surface (fog_volume.glsl) -in vec2 f_coords; +layout(location = 14) in vec2 f_coords; -#include -#include +#include "common.glsl" +#include "fog_volume.glsl" layout(location = 0) out vec4 out_color; layout(binding = 0) uniform sampler2D depth_tex; diff --git a/shaders/postfx_ssao_blur.frag b/shaders/ogl46/postfx_ssao_blur.frag similarity index 64% rename from shaders/postfx_ssao_blur.frag rename to shaders/ogl46/postfx_ssao_blur.frag index e3cbfb0b..bb83cb21 100644 --- a/shaders/postfx_ssao_blur.frag +++ b/shaders/ogl46/postfx_ssao_blur.frag @@ -1,8 +1,10 @@ -in vec2 f_coords; +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 14) in vec2 f_coords; layout(location = 0) out vec4 out_color; -#texture (ssao_tex, 0, R) -uniform sampler2D ssao_tex; +#pragma texture(ssao_tex, 0, R) +layout(binding = 0) uniform sampler2D ssao_tex; void main() { vec2 texel = 1.0 / vec2(textureSize(ssao_tex, 0)); diff --git a/shaders/postfx_tonemapping.frag b/shaders/ogl46/postfx_tonemapping.frag similarity index 78% rename from shaders/postfx_tonemapping.frag rename to shaders/ogl46/postfx_tonemapping.frag index 90134147..6649ace6 100644 --- a/shaders/postfx_tonemapping.frag +++ b/shaders/ogl46/postfx_tonemapping.frag @@ -1,15 +1,17 @@ -in vec2 f_coords; +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 14) in vec2 f_coords; layout(location = 0) out vec4 out_color; -#texture (tex1, 0, RGB) -uniform sampler2D tex1; +#pragma texture(tex1, 0, RGB) +layout(binding = 0) uniform sampler2D tex1; // glow of the bright parts of the image (postfx_bloom_*.frag) -#texture (bloom_tex, 1, RGB) -uniform sampler2D bloom_tex; +#pragma texture(bloom_tex, 1, RGB) +layout(binding = 1) uniform sampler2D bloom_tex; -#include +#include "common.glsl" // exposure worked out by exposure_average.comp layout(std430, binding = EXPOSURE_SSBO_BINDING) readonly buffer exposure_ssbo @@ -21,8 +23,8 @@ layout(std430, binding = EXPOSURE_SSBO_BINDING) readonly buffer exposure_ssbo uint valid; }; -#include -#include +#include "tonemapping.glsl" +#include "dither.glsl" // param[0].x: share of the bloom in the image (0: no bloom, the texture is not read), param[0].y: 1 if the automatic // exposure is applied, param[0].z: fixed exposure multiplier, param[0].w: scale of the bloom texture (it holds the sum of diff --git a/shaders/ogl46/precipitation.frag b/shaders/ogl46/precipitation.frag new file mode 100644 index 00000000..350f45c7 --- /dev/null +++ b/shaders/ogl46/precipitation.frag @@ -0,0 +1,37 @@ +#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" + +layout(location = 0) out vec4 out_color; +layout(location = 1) out vec4 out_motion; + +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; + +#include "tonemapping.glsl" + +void main() +{ + vec4 tex_color = texture(tex1, vec2(f_coord.x, f_coord.y + param[1].x)); + vec4 color = tex_color * param[0]; + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } + 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, tex_color.a * alpha_mult); + } +} diff --git a/shaders/precipitation.vert b/shaders/ogl46/precipitation.vert similarity index 59% rename from shaders/precipitation.vert rename to shaders/ogl46/precipitation.vert index 4b33ccac..a7fd60df 100644 --- a/shaders/precipitation.vert +++ b/shaders/ogl46/precipitation.vert @@ -1,12 +1,14 @@ +#version 460 core +#define STAGE_VERTEX 1 layout(location = 0) in vec3 v_vert; layout(location = 1) in vec2 v_coord; -out vec2 f_coord; +layout(location = 2) out vec2 f_coord; -out vec4 f_clip_pos; -out vec4 f_clip_future_pos; +layout(location = 4) out vec4 f_clip_pos; +layout(location = 5) out vec4 f_clip_future_pos; -#include +#include "common.glsl" void main() { diff --git a/shaders/quad.vert b/shaders/ogl46/quad.vert similarity index 79% rename from shaders/quad.vert rename to shaders/ogl46/quad.vert index 850d3fee..087d3630 100644 --- a/shaders/quad.vert +++ b/shaders/ogl46/quad.vert @@ -1,3 +1,5 @@ +#version 460 core +#define STAGE_VERTEX 1 const vec2 vert[4] = vec2[] ( vec2(-1.0, 1.0), @@ -14,7 +16,7 @@ const vec2 uv[4] = vec2[] vec2(1.0, 0.0) ); -out vec2 f_coords; +layout(location = 14) out vec2 f_coords; void main() { diff --git a/shaders/random.glsl b/shaders/ogl46/random.glsl similarity index 100% rename from shaders/random.glsl rename to shaders/ogl46/random.glsl diff --git a/shaders/ogl46/settings.glsl b/shaders/ogl46/settings.glsl new file mode 100644 index 00000000..65c44199 --- /dev/null +++ b/shaders/ogl46/settings.glsl @@ -0,0 +1,49 @@ +// the settings of the game the shaders depend on (gfx.* in eu07.ini). in the SPIR-V binaries (shaders/ogl46bin) they are +// specialization constants the game sets when it loads a binary (gl::shader_setting in gl/shader.h has the ids); compiled +// from the source, constants of the values the game puts before the source (the defaults below otherwise) +#ifndef SETTINGS_GLSL +#define SETTINGS_GLSL + +#ifdef GL_SPIRV +layout(constant_id = 0) const bool SHADOWMAP_ENABLED = true; +layout(constant_id = 1) const bool ENVMAP_ENABLED = true; +layout(constant_id = 2) const bool MOTIONBLUR_ENABLED = true; +// the colour goes on to the post-processing; tonemapped by the shaders of the scene otherwise +layout(constant_id = 3) const bool POSTFX_ENABLED = true; +layout(constant_id = 4) const bool EXTRAEFFECTS_ENABLED = true; +// whether the colour written to the window is encoded by the shader (FBOUT) or left to the sRGB framebuffer (dither.glsl) +layout(constant_id = 5) const bool OUTPUT_GAMMA = false; +#else +#ifndef SHADOWMAP_ENABLED_VALUE +#define SHADOWMAP_ENABLED_VALUE true +#endif +#ifndef ENVMAP_ENABLED_VALUE +#define ENVMAP_ENABLED_VALUE true +#endif +#ifndef MOTIONBLUR_ENABLED_VALUE +#define MOTIONBLUR_ENABLED_VALUE true +#endif +#ifndef POSTFX_ENABLED_VALUE +#define POSTFX_ENABLED_VALUE true +#endif +#ifndef EXTRAEFFECTS_ENABLED_VALUE +#define EXTRAEFFECTS_ENABLED_VALUE true +#endif +#ifndef OUTPUT_GAMMA_VALUE +#define OUTPUT_GAMMA_VALUE false +#endif +const bool SHADOWMAP_ENABLED = SHADOWMAP_ENABLED_VALUE; +const bool ENVMAP_ENABLED = ENVMAP_ENABLED_VALUE; +const bool MOTIONBLUR_ENABLED = MOTIONBLUR_ENABLED_VALUE; +const bool POSTFX_ENABLED = POSTFX_ENABLED_VALUE; +const bool EXTRAEFFECTS_ENABLED = EXTRAEFFECTS_ENABLED_VALUE; +const bool OUTPUT_GAMMA = OUTPUT_GAMMA_VALUE; +#endif + +// the colour written to the window +vec4 FBOUT(vec4 x) +{ + return (OUTPUT_GAMMA ? vec4(pow(x.rgb, vec3(1.0 / 2.2)), x.a) : x); +} + +#endif diff --git a/shaders/shadow_fit_bounds.comp b/shaders/ogl46/shadow_fit_bounds.comp similarity index 98% rename from shaders/shadow_fit_bounds.comp rename to shaders/ogl46/shadow_fit_bounds.comp index 2112a48f..40b98e2b 100644 --- a/shaders/shadow_fit_bounds.comp +++ b/shaders/ogl46/shadow_fit_bounds.comp @@ -1,10 +1,12 @@ +#version 460 core +#define STAGE_COMPUTE 1 // the cascades of the sun shadows fitted to the depth of the main view (opengl46_shadow_fit), second pass: the distances // from the nearest to the farthest pixel which isn't the sky (shadow_fit_depth.comp) split between the cascades, and for each // cascade the box of the pixels it takes, in the space of the light. the cpu makes the maps of the cascades of a frame or // two later cover the boxes: the camera has moved since, so a cascade takes the pixels a little past its distances as well, // and the shading goes to the next cascade with a point outside the map of its own (light_common.glsl) -#include +#include "common.glsl" layout(local_size_x = SHADOW_FIT_GROUP_SIZE, local_size_y = SHADOW_FIT_GROUP_SIZE) in; diff --git a/shaders/shadow_fit_depth.comp b/shaders/ogl46/shadow_fit_depth.comp similarity index 96% rename from shaders/shadow_fit_depth.comp rename to shaders/ogl46/shadow_fit_depth.comp index 2f2dee18..c70b2677 100644 --- a/shaders/shadow_fit_depth.comp +++ b/shaders/ogl46/shadow_fit_depth.comp @@ -1,9 +1,11 @@ +#version 460 core +#define STAGE_COMPUTE 1 // the cascades of the sun shadows fitted to the depth of the main view (opengl46_shadow_fit), first pass: how many pixels of // the resolved depth of the view aren't the sky, and the nearest and the farthest of their distances from the camera. // the depth is reversed with the far plane at infinity: the sky, which writes no depth, stays at 0. // shadow_fit_bounds.comp splits the distances between the cascades -#include +#include "common.glsl" layout(local_size_x = SHADOW_FIT_GROUP_SIZE, local_size_y = SHADOW_FIT_GROUP_SIZE) in; diff --git a/shaders/shadowcube.geom b/shaders/ogl46/shadowcube.geom similarity index 93% rename from shaders/shadowcube.geom rename to shaders/ogl46/shadowcube.geom index 96b3f821..583d7371 100644 --- a/shaders/shadowcube.geom +++ b/shaders/ogl46/shadowcube.geom @@ -1,3 +1,5 @@ +#version 460 core +#define STAGE_GEOMETRY 1 // draws the geometry into the six faces of a cube shadow map at once, one invocation per face. the input is in the space of the // map, with the lamp at the origin. the faces go in the order of the faces of a cube map (+x, -x, +y, -y, +z, -z), each looks the // way glm::lookAt() with the usual up vectors of cube maps sets it, with a 90 degree projection of infinite reach and reversed @@ -5,9 +7,9 @@ layout(triangles, invocations = 6) in; layout(triangle_strip, max_vertices = 3) out; -in vec3 g_pos[]; -in vec2 g_coord[]; -out vec2 f_coord; +layout(location = 20) in vec3 g_pos[]; +layout(location = 18) in vec2 g_coord[]; +layout(location = 2) out vec2 f_coord; // near plane of the map, metres: closer surfaces (the lamp itself) cast no shadows layout(location = 0) uniform float cube_near; diff --git a/shaders/shadowcube.vert b/shaders/ogl46/shadowcube.vert similarity index 68% rename from shaders/shadowcube.vert rename to shaders/ogl46/shadowcube.vert index c8a2a54d..3f5736e0 100644 --- a/shaders/shadowcube.vert +++ b/shaders/ogl46/shadowcube.vert @@ -1,11 +1,13 @@ +#version 460 core +#define STAGE_VERTEX 1 layout(location = 0) in vec3 v_vert; layout(location = 2) in vec2 v_coord; // position in the space of the cube shadow map being drawn (origin at the lamp), see shadowcube.geom -out vec3 g_pos; -out vec2 g_coord; +layout(location = 20) out vec3 g_pos; +layout(location = 18) out vec2 g_coord; -#include +#include "common.glsl" void main() { diff --git a/shaders/ogl46/shadowmap.frag b/shaders/ogl46/shadowmap.frag new file mode 100644 index 00000000..557eddd5 --- /dev/null +++ b/shaders/ogl46/shadowmap.frag @@ -0,0 +1,7 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 2) in vec2 f_coord; + +void main() +{ +} diff --git a/shaders/simpleuv.vert b/shaders/ogl46/simpleuv.vert similarity index 70% rename from shaders/simpleuv.vert rename to shaders/ogl46/simpleuv.vert index 13cd189a..0201dbfc 100644 --- a/shaders/simpleuv.vert +++ b/shaders/ogl46/simpleuv.vert @@ -1,9 +1,11 @@ +#version 460 core +#define STAGE_VERTEX 1 layout(location = 0) in vec3 v_vert; layout(location = 2) in vec2 v_coord; -out vec2 f_coord; +layout(location = 2) out vec2 f_coord; -#include +#include "common.glsl" void main() { diff --git a/shaders/sky_atmosphere.frag b/shaders/ogl46/sky_atmosphere.frag similarity index 81% rename from shaders/sky_atmosphere.frag rename to shaders/ogl46/sky_atmosphere.frag index 4556e72f..3d6f8909 100644 --- a/shaders/sky_atmosphere.frag +++ b/shaders/ogl46/sky_atmosphere.frag @@ -1,24 +1,24 @@ +#version 460 core +#define STAGE_FRAGMENT 1 // the sky dome drawn with the light of the physical atmosphere (atmosphere_common.glsl): the sky-view table looked up in // the direction of each fragment. the colour of the old dome (vertex colours, see color.frag) is kept for the night, // which the atmosphere does not cover (no moon, no stars), and the two are blended by the height of the sun. // param[0].x: share of the atmosphere, param[0].y: brightness of the atmosphere, param[0].z: how much the overcast flattens // the sky, param[0].w: luminance the flattened sky tends to. param[1].rgb: white balance of the atmosphere -in vec3 f_color; -in vec4 f_pos; +layout(location = 13) in vec3 f_color; +layout(location = 3) in vec4 f_pos; -#include -#include -#include -#include -#include +#include "common.glsl" +#include "atmosphere_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" +#include "sky_shoulder.glsl" layout(binding = SKY_VIEW_TEX) uniform sampler2D sky_view_lut; layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED layout(location = 1) out vec4 out_motion; -#endif // the brightest the sky gets, a guard against the circumsolar peak of the mie scattering blowing up the bloom const float SKY_RADIANCE_MAX = 64.0; @@ -43,12 +43,16 @@ void main() vec3 col = mix(domecolor, atmosphere, param[0].x); if (any(isnan(col)) || any(isinf(col))) col = domecolor; -#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 + if (POSTFX_ENABLED) + { + out_color = vec4(apply_fog(col), 1.0f); + } + else + { + out_color = tonemap(vec4(apply_fog(col), 1.0f)); + } + if (MOTIONBLUR_ENABLED) + { + out_motion = vec4(0.0f); + } } diff --git a/shaders/sky_shoulder.glsl b/shaders/ogl46/sky_shoulder.glsl similarity index 100% rename from shaders/sky_shoulder.glsl rename to shaders/ogl46/sky_shoulder.glsl diff --git a/shaders/sky_weather.frag b/shaders/ogl46/sky_weather.frag similarity index 82% rename from shaders/sky_weather.frag rename to shaders/ogl46/sky_weather.frag index c1114818..36bb3981 100644 --- a/shaders/sky_weather.frag +++ b/shaders/ogl46/sky_weather.frag @@ -1,13 +1,15 @@ +#version 460 core +#define STAGE_FRAGMENT 1 // laid over the sky after the dome, the stars, the sun and the moon (opengl46_weather::draw_sky): the clouds (clouds_march.comp) // and the fog in front of them, which hides the sky and the clouds towards the horizon. blended with (1, source alpha): the colour is // what the clouds and the fog add, the alpha what they let through of the sky behind. // mode 0: no clouds, 1: the clouds of the main view (an image over the screen), 2: the panorama of the clouds around the camera -in vec2 f_coords; +layout(location = 14) in vec2 f_coords; -#include -#include -#include +#include "common.glsl" +#include "fog_volume.glsl" +#include "tonemapping.glsl" layout(binding = CLOUD_TEX) uniform sampler2D clouds; @@ -16,9 +18,7 @@ layout(location = 1) uniform mat4 view_to_world; layout(location = 2) uniform int mode; layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED layout(location = 1) out vec4 out_motion; -#endif // as in clouds_march.comp const float PANORAMA_LOW = -0.1; @@ -41,12 +41,16 @@ void main() vec4 fog = fog_sky(near.xyz / near.w, gl_FragCoord.xy); vec3 color = fog.rgb + fog.a * cloud.rgb; float through = fog.a * cloud.a; -#if POSTFX_ENABLED - out_color = vec4(color, through); -#else - out_color = vec4(tonemap(vec4(color, 1.0)).rgb, through); -#endif -#if MOTIONBLUR_ENABLED - out_motion = vec4(0.0); -#endif + if (POSTFX_ENABLED) + { + out_color = vec4(color, through); + } + else + { + out_color = vec4(tonemap(vec4(color, 1.0)).rgb, through); + } + if (MOTIONBLUR_ENABLED) + { + out_motion = vec4(0.0); + } } diff --git a/shaders/ogl46/smoke.frag b/shaders/ogl46/smoke.frag new file mode 100644 index 00000000..d8164e3a --- /dev/null +++ b/shaders/ogl46/smoke.frag @@ -0,0 +1,38 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 13) in vec4 f_color; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; + +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; + +#pragma texture(tex1, 0, sRGB_A) +layout(binding = 0) uniform sampler2D tex1; + +#include "common.glsl" +#include "apply_fog.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) * f_color; + if (POSTFX_ENABLED) + { + out_color = vec4(apply_fog(tex_color.rgb), tex_color.a); + } + else + { + out_color = tonemap(vec4(apply_fog(tex_color.rgb), tex_color.a)); + } + 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, tex_color.a * alpha_mult); + } +} diff --git a/shaders/smoke.vert b/shaders/ogl46/smoke.vert similarity index 58% rename from shaders/smoke.vert rename to shaders/ogl46/smoke.vert index 8769d067..82bac6fa 100644 --- a/shaders/smoke.vert +++ b/shaders/ogl46/smoke.vert @@ -1,15 +1,17 @@ +#version 460 core +#define STAGE_VERTEX 1 layout(location = 0) in vec3 v_vert; layout(location = 1) in vec4 v_color; layout(location = 2) in vec2 v_coord; -out vec4 f_pos; -out vec4 f_color; -out vec2 f_coord; +layout(location = 3) out vec4 f_pos; +layout(location = 13) out vec4 f_color; +layout(location = 2) out vec2 f_coord; -out vec4 f_clip_pos; -out vec4 f_clip_future_pos; +layout(location = 4) out vec4 f_clip_pos; +layout(location = 5) out vec4 f_clip_future_pos; -#include +#include "common.glsl" void main() { diff --git a/shaders/ogl46/stars.frag b/shaders/ogl46/stars.frag new file mode 100644 index 00000000..2bd941fb --- /dev/null +++ b/shaders/ogl46/stars.frag @@ -0,0 +1,30 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +// a star: a soft point added to the sky + +layout(location = 13) in vec3 f_color; + +#include "common.glsl" +#include "tonemapping.glsl" + +layout(location = 0) out vec4 out_color; +layout(location = 1) out vec4 out_motion; + +void main() +{ + vec2 offset = gl_PointCoord - 0.5; + float spot = exp(-dot(offset, offset) * 14.0); + vec3 color = f_color * spot; + if (POSTFX_ENABLED) + { + out_color = vec4(color, 0.0); + } + else + { + out_color = vec4(tonemap(vec4(color, 1.0)).rgb, 0.0); + } + if (MOTIONBLUR_ENABLED) + { + out_motion = vec4(0.0); + } +} diff --git a/shaders/stars.vert b/shaders/ogl46/stars.vert similarity index 94% rename from shaders/stars.vert rename to shaders/ogl46/stars.vert index 88309fb2..7e3d782d 100644 --- a/shaders/stars.vert +++ b/shaders/ogl46/stars.vert @@ -1,10 +1,12 @@ +#version 460 core +#define STAGE_VERTEX 1 // the stars of the catalogue (environment/starcatalog.h) as points: turned from the equatorial frame to the sky of the observer // by the sidereal time and the latitude, dimmed through the air towards the horizon and twinkling there layout(location = 0) in vec4 v_star; // xyz: direction in the equatorial frame (z to the north pole), w: visual magnitude layout(location = 1) in vec4 v_color; // rgb: colour of the star, w: a random phase of the twinkling -#include +#include "common.glsl" // equatorial frame to world axes (x west, y up, z north), and world axes to the view layout(location = 0) uniform mat4 equatorial_to_world; @@ -12,7 +14,7 @@ layout(location = 1) uniform mat4 world_to_view; // x: brightness of a star of magnitude 0, y: seconds, z: size of the points in pixels layout(location = 2) uniform vec4 params; -out vec3 f_color; +layout(location = 13) out vec3 f_color; void main() { diff --git a/shaders/ogl46/texturewindow.frag b/shaders/ogl46/texturewindow.frag new file mode 100644 index 00000000..af241b66 --- /dev/null +++ b/shaders/ogl46/texturewindow.frag @@ -0,0 +1,18 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +layout(location = 14) in vec2 f_coords; + +layout(location = 0) out vec4 out_color; + +#include "settings.glsl" + +#pragma texture(tex1, 0, sRGB) +layout(binding = 0) uniform sampler2D tex1; + +void main() +{ + vec2 texcoord = f_coords; + vec3 color = texture(tex1, texcoord).rgb; + + out_color = FBOUT(vec4(color, 1.0)); +} diff --git a/shaders/texturewindow.vert b/shaders/ogl46/texturewindow.vert similarity index 76% rename from shaders/texturewindow.vert rename to shaders/ogl46/texturewindow.vert index 47e7ecf6..74069d09 100644 --- a/shaders/texturewindow.vert +++ b/shaders/ogl46/texturewindow.vert @@ -1,4 +1,6 @@ -layout (std140) uniform scene_ubo +#version 460 core +#define STAGE_VERTEX 1 +layout(std140, binding = 0) uniform scene_ubo { mat4 projection; mat4 lightview; @@ -21,7 +23,7 @@ const vec2 uv[4] = vec2[] vec2(1.0, 0.0) ); -out vec2 f_coords; +layout(location = 14) out vec2 f_coords; void main() { diff --git a/shaders/tonemapping.glsl b/shaders/ogl46/tonemapping.glsl similarity index 99% rename from shaders/tonemapping.glsl rename to shaders/ogl46/tonemapping.glsl index 863fe0fe..b2b8ae1a 100644 --- a/shaders/tonemapping.glsl +++ b/shaders/ogl46/tonemapping.glsl @@ -1,3 +1,5 @@ +#include "settings.glsl" + const float pureWhite = 1.0; vec3 reinhard(vec3 x) diff --git a/shaders/trackblend.glsl b/shaders/ogl46/trackblend.glsl similarity index 71% rename from shaders/trackblend.glsl rename to shaders/ogl46/trackblend.glsl index a08e23dd..9a0f62be 100644 --- a/shaders/trackblend.glsl +++ b/shaders/ogl46/trackblend.glsl @@ -6,36 +6,34 @@ // TRACKBLEND_SPECGLOSS the specgloss map of the track itself as well, the textures of the ends p1 and p2 alone (no room // for a third set of them) // TRACKBLEND_PARALLAX with the normal maps, as mat_parallax: each texture shifted by the heights in its own normal map -in vec3 f_normal; -in vec2 f_coord; -in vec4 f_pos; +layout(location = 0) in vec3 f_normal; +layout(location = 2) in vec2 f_coord; +layout(location = 3) in vec4 f_pos; #ifdef TRACKBLEND_NORMALMAP -in mat3 f_tbn; +layout(location = 8) in mat3 f_tbn; #endif #ifdef TRACKBLEND_PARALLAX -in vec3 TangentFragPos; +layout(location = 6) in vec3 TangentFragPos; #endif -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; -#include +#include "common.glsl" -#param (color, 0, 0, 4, diffuse) -#param (diffuse, 1, 0, 1, diffuse) -#param (specular, 1, 1, 1, specular) -#param (reflection, 1, 2, 1, zero) -#param (glossiness, 1, 3, 1, glossiness) -#param (blendlighting, 2, 0, 1, zero) +#pragma param(color, 0, 0, 4, diffuse) +#pragma param(diffuse, 1, 0, 1, diffuse) +#pragma param(specular, 1, 1, 1, specular) +#pragma param(reflection, 1, 2, 1, zero) +#pragma param(glossiness, 1, 3, 1, glossiness) +#pragma param(blendlighting, 2, 0, 1, zero) layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED layout(location = 1) out vec4 out_motion; -#endif -#include -#include -#include +#include "light_common.glsl" +#include "apply_fog.glsl" +#include "tonemapping.glsl" #ifdef TRACKBLEND_PARALLAX // the texture coordinates shifted by the heights (b) of the normal map, as ParallaxMapping() of mat_parallax.frag @@ -45,34 +43,37 @@ vec2 parallax_coord(sampler2D heights, vec2 coord, vec3 viewdir, vec2 dcoorddx, if (pos_len > 100.0) return coord; 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 = coord; - float currentDepthMapValue = textureGrad(heights, 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)); - float layerDepth = 1.0 / numLayers; - float currentLayerDepth = 0.0; - vec2 P = viewdir.xy * param[2].y * fade; - vec2 deltaTexCoords = P / numLayers; - while (currentLayerDepth < currentDepthMapValue) + if (EXTRAEFFECTS_ENABLED) { - currentTexCoords -= deltaTexCoords; - currentDepthMapValue = textureGrad(heights, currentTexCoords, dcoorddx, dcoorddy).b; - currentLayerDepth += layerDepth; + const float minLayers = 8.0; + const float maxLayers = 32.0; + float LayersWeight = pos_len / 20.0; + vec2 currentTexCoords = coord; + float currentDepthMapValue = textureGrad(heights, 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)); + float layerDepth = 1.0 / numLayers; + float currentLayerDepth = 0.0; + vec2 P = viewdir.xy * param[2].y * fade; + vec2 deltaTexCoords = P / numLayers; + while (currentLayerDepth < currentDepthMapValue) + { + currentTexCoords -= deltaTexCoords; + currentDepthMapValue = textureGrad(heights, currentTexCoords, dcoorddx, dcoorddy).b; + currentLayerDepth += layerDepth; + } + vec2 prevTexCoords = currentTexCoords + deltaTexCoords; + float afterDepth = currentDepthMapValue - currentLayerDepth; + float beforeDepth = textureGrad(heights, prevTexCoords, dcoorddx, dcoorddy).b - currentLayerDepth + layerDepth; + float weight = afterDepth / (afterDepth - beforeDepth); + return prevTexCoords * weight + currentTexCoords * (1.0 - weight); + } + else + { + float height = textureGrad(heights, coord, dcoorddx, dcoorddy).b; + vec2 p = viewdir.xy / viewdir.z * (height * (param[2].y - param[2].z) * 0.2) * fade; + return coord - p; } - vec2 prevTexCoords = currentTexCoords + deltaTexCoords; - float afterDepth = currentDepthMapValue - currentLayerDepth; - float beforeDepth = textureGrad(heights, prevTexCoords, dcoorddx, dcoorddy).b - currentLayerDepth + layerDepth; - float weight = afterDepth / (afterDepth - beforeDepth); - return prevTexCoords * weight + currentTexCoords * (1.0 - weight); -#else - float height = textureGrad(heights, coord, dcoorddx, dcoorddy).b; - vec2 p = viewdir.xy / viewdir.z * (height * (param[2].y - param[2].z) * 0.2) * fade; - return coord - p; -#endif } // each texture at the coordinates shifted by the heights of its own normal map #define TRACKBLEND_COORD(heights) parallax_coord(heights, f_coord, viewdir, dx, dy) @@ -180,18 +181,20 @@ void main() fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, 0.0, 0.0, shadow_tone); #endif vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult); -#if POSTFX_ENABLED - out_color = color; -#else - out_color = tonemap(color); -#endif + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } -#if MOTIONBLUR_ENABLED + 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, tex_color.a * alpha_mult); } -#endif } diff --git a/shaders/ogl46/traction.frag b/shaders/ogl46/traction.frag new file mode 100644 index 00000000..f8438f81 --- /dev/null +++ b/shaders/ogl46/traction.frag @@ -0,0 +1,34 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +#include "common.glsl" + +layout(location = 3) in vec4 f_pos; +layout(location = 4) in vec4 f_clip_pos; +layout(location = 5) in vec4 f_clip_future_pos; + +layout(location = 0) out vec4 out_color; +layout(location = 1) out vec4 out_motion; + +#include "tonemapping.glsl" +#include "apply_fog.glsl" + +void main() +{ + vec4 color = vec4(apply_fog(pow(param[0].rgb, vec3(2.2))), param[0].a); + if (POSTFX_ENABLED) + { + out_color = color; + } + else + { + out_color = tonemap(color); + } + + 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); + } +} diff --git a/shaders/traction.vert b/shaders/ogl46/traction.vert similarity index 63% rename from shaders/traction.vert rename to shaders/ogl46/traction.vert index 55d5bd9a..563663f4 100644 --- a/shaders/traction.vert +++ b/shaders/ogl46/traction.vert @@ -1,12 +1,14 @@ +#version 460 core +#define STAGE_VERTEX 1 layout(location = 0) in vec3 v_vert; layout(location = 1) in vec3 v_normal; layout(location = 2) in vec2 v_coord; -#include +#include "common.glsl" -out vec4 f_clip_pos; -out vec4 f_clip_future_pos; -out vec4 f_pos; +layout(location = 4) out vec4 f_clip_pos; +layout(location = 5) out vec4 f_clip_future_pos; +layout(location = 3) out vec4 f_pos; void main() { diff --git a/shaders/vbocolor.vert b/shaders/ogl46/vbocolor.vert similarity index 58% rename from shaders/vbocolor.vert rename to shaders/ogl46/vbocolor.vert index dce7511d..7031a5dc 100644 --- a/shaders/vbocolor.vert +++ b/shaders/ogl46/vbocolor.vert @@ -1,11 +1,13 @@ +#version 460 core +#define STAGE_VERTEX 1 layout(location = 0) in vec3 v_vert; layout(location = 1) in vec3 v_color; -out vec3 f_color; -out vec4 f_pos; -out vec4 f_clip_pos; +layout(location = 13) out vec3 f_color; +layout(location = 3) out vec4 f_pos; +layout(location = 4) out vec4 f_clip_pos; -#include +#include "common.glsl" void main() { diff --git a/shaders/vertex.vert b/shaders/ogl46/vertex.vert similarity index 74% rename from shaders/vertex.vert rename to shaders/ogl46/vertex.vert index b042783c..8dd7f49a 100644 --- a/shaders/vertex.vert +++ b/shaders/ogl46/vertex.vert @@ -1,27 +1,29 @@ +#version 460 core +#define STAGE_VERTEX 1 layout(location = 0) in vec3 v_vert; layout(location = 1) in vec3 v_normal; layout(location = 2) in vec2 v_coord; layout(location = 3) in vec4 v_tangent; layout(location = 4) in vec4 v_userdata; -#include +#include "common.glsl" // the depth prepass (depthprepass.vert) works the position out the same way, and has to get the same depth invariant gl_Position; -out vec3 f_normal; -flat out vec3 f_normal_raw; -out vec2 f_coord; -out vec4 f_pos; -out mat3 f_tbn; +layout(location = 0) out vec3 f_normal; +layout(location = 1) flat out vec3 f_normal_raw; +layout(location = 2) out vec2 f_coord; +layout(location = 3) out vec4 f_pos; +layout(location = 8) out mat3 f_tbn; -out vec4 f_clip_pos; -out vec4 f_clip_future_pos; +layout(location = 4) out vec4 f_clip_pos; +layout(location = 5) out vec4 f_clip_future_pos; //out vec3 TangentLightPos; //out vec3 TangentViewPos; -out vec3 TangentFragPos; -out vec4 UserData; +layout(location = 6) out vec3 TangentFragPos; +layout(location = 7) out vec4 UserData; void main() { diff --git a/shaders/vertexonly.vert b/shaders/ogl46/vertexonly.vert similarity index 66% rename from shaders/vertexonly.vert rename to shaders/ogl46/vertexonly.vert index 370b4aa3..a5996162 100644 --- a/shaders/vertexonly.vert +++ b/shaders/ogl46/vertexonly.vert @@ -1,6 +1,8 @@ +#version 460 core +#define STAGE_VERTEX 1 layout(location = 0) in vec3 v_vert; -#include +#include "common.glsl" void main() { diff --git a/shaders/weather_common.glsl b/shaders/ogl46/weather_common.glsl similarity index 100% rename from shaders/weather_common.glsl rename to shaders/ogl46/weather_common.glsl diff --git a/shaders/ogl46bin/README.md b/shaders/ogl46bin/README.md new file mode 100644 index 00000000..288d2f93 --- /dev/null +++ b/shaders/ogl46bin/README.md @@ -0,0 +1 @@ +SPIR-V binaries of `shaders/ogl46/`, made by `shaders/compile_spirv.py` (see `shaders/README.md`). diff --git a/shaders/particles_smoke.frag b/shaders/particles_smoke.frag deleted file mode 100644 index 10a5eeef..00000000 --- a/shaders/particles_smoke.frag +++ /dev/null @@ -1,42 +0,0 @@ -// the smoke of the gpu (opengl46_gpu_particles, particles_smoke.vert): the texture of the smoke in the colour the compute pass -// lit the particle with, faded in front of the scene, fogged - -in vec4 f_color; -in vec2 f_coord; -in vec4 f_pos; -in float f_soft; - -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; - -#texture (tex1, 0, sRGB_A) -uniform sampler2D tex1; - -#include -#include -#include -#include - -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) * f_color; - tex_color.a *= particle_soft_fade(f_soft, -f_pos.z); -#if POSTFX_ENABLED - out_color = vec4(apply_fog(tex_color.rgb), tex_color.a); -#else - out_color = tonemap(vec4(apply_fog(tex_color.rgb), tex_color.a)); -#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, tex_color.a * alpha_mult); - } -#endif -} diff --git a/shaders/precipitation.frag b/shaders/precipitation.frag deleted file mode 100644 index 2b1ea6a4..00000000 --- a/shaders/precipitation.frag +++ /dev/null @@ -1,35 +0,0 @@ -in vec2 f_coord; - -#texture (tex1, 0, sRGB_A) -uniform sampler2D tex1; - -#include - -layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED -layout(location = 1) out vec4 out_motion; -#endif - -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; - -#include - -void main() -{ - vec4 tex_color = texture(tex1, vec2(f_coord.x, f_coord.y + param[1].x)); - vec4 color = tex_color * param[0]; -#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, tex_color.a * alpha_mult); - } -#endif -} diff --git a/shaders/shadowmap.frag b/shaders/shadowmap.frag deleted file mode 100644 index 14ef7549..00000000 --- a/shaders/shadowmap.frag +++ /dev/null @@ -1,5 +0,0 @@ -in vec2 f_coord; - -void main() -{ -} diff --git a/shaders/smoke.frag b/shaders/smoke.frag deleted file mode 100644 index b790ef3c..00000000 --- a/shaders/smoke.frag +++ /dev/null @@ -1,36 +0,0 @@ -in vec4 f_color; -in vec2 f_coord; -in vec4 f_pos; - -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; - -#texture (tex1, 0, sRGB_A) -uniform sampler2D tex1; - -#include -#include -#include - -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) * f_color; -#if POSTFX_ENABLED - out_color = vec4(apply_fog(tex_color.rgb), tex_color.a); -#else - out_color = tonemap(vec4(apply_fog(tex_color.rgb), tex_color.a)); -#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, tex_color.a * alpha_mult); - } -#endif -} diff --git a/shaders/stars.frag b/shaders/stars.frag deleted file mode 100644 index 0f1e66e6..00000000 --- a/shaders/stars.frag +++ /dev/null @@ -1,26 +0,0 @@ -// a star: a soft point added to the sky - -in vec3 f_color; - -#include -#include - -layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED -layout(location = 1) out vec4 out_motion; -#endif - -void main() -{ - vec2 offset = gl_PointCoord - 0.5; - float spot = exp(-dot(offset, offset) * 14.0); - vec3 color = f_color * spot; -#if POSTFX_ENABLED - out_color = vec4(color, 0.0); -#else - out_color = vec4(tonemap(vec4(color, 1.0)).rgb, 0.0); -#endif -#if MOTIONBLUR_ENABLED - out_motion = vec4(0.0); -#endif -} diff --git a/shaders/texturewindow.frag b/shaders/texturewindow.frag deleted file mode 100644 index 80a3ebc8..00000000 --- a/shaders/texturewindow.frag +++ /dev/null @@ -1,14 +0,0 @@ -in vec2 f_coords; - -layout(location = 0) out vec4 out_color; - -#texture (tex1, 0, sRGB) -uniform sampler2D tex1; - -void main() -{ - vec2 texcoord = f_coords; - vec3 color = texture(tex1, texcoord).rgb; - - out_color = FBOUT(vec4(color, 1.0)); -} diff --git a/shaders/traction.frag b/shaders/traction.frag deleted file mode 100644 index f80639df..00000000 --- a/shaders/traction.frag +++ /dev/null @@ -1,32 +0,0 @@ -#include - -in vec4 f_pos; -in vec4 f_clip_pos; -in vec4 f_clip_future_pos; - -layout(location = 0) out vec4 out_color; -#if MOTIONBLUR_ENABLED -layout(location = 1) out vec4 out_motion; -#endif - -#include -#include - -void main() -{ - vec4 color = vec4(apply_fog(pow(param[0].rgb, vec3(2.2))), param[0].a); -#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 -} diff --git a/shaders/vk14/common.glsl b/shaders/vk14/common.glsl new file mode 100644 index 00000000..e5e920fe --- /dev/null +++ b/shaders/vk14/common.glsl @@ -0,0 +1,32 @@ +// what the shaders of the vulkan 1.4 renderer share: the bindings of rendering/vulkan14/vulkan14bindings.h and the settings. +// compiled by the game, the bindings come from the renderer (vk14::binding_preamble()) and are taken instead of these; the +// SPIR-V binaries (shaders/vk14bin) are compiled with the values written here, so they have to stay the same as in the game, +// which checks them when it starts (and doesn't load the binaries when they differ) +#ifndef COMMON_GLSL +#define COMMON_GLSL + +#if !defined(STAGE_VERTEX) && !defined(STAGE_FRAGMENT) && !defined(STAGE_GEOMETRY) && !defined(STAGE_COMPUTE) +#error "the stage of the shader isn't defined: #define STAGE_VERTEX 1 (STAGE_FRAGMENT, STAGE_GEOMETRY, STAGE_COMPUTE) before the include of common.glsl" +#endif + +#ifndef MASZYNA_CONSTANTS +#define MASZYNA_CONSTANTS 1 +#define UI_SET 0 +#define UI_TEXTURE 0 +#define PASS_SET 0 +#define MATERIAL_SET 1 +#define MATERIAL_TEXTURE_FIRST 0 +#define SPEC_OUTPUT_GAMMA 0 +#endif + +// whether the colour written to the window is encoded by the shader (FBOUT): the window has no srgb format (gfx.shadergamma yes). +// a specialization constant, set by the renderer when it makes the pipeline +layout(constant_id = SPEC_OUTPUT_GAMMA) const bool OUTPUT_GAMMA = false; + +// the colour written to the window +vec4 FBOUT(vec4 x) +{ + return (OUTPUT_GAMMA ? vec4(pow(x.rgb, vec3(1.0 / 2.2)), x.a) : x); +} + +#endif diff --git a/shaders/vk14/imgui.frag b/shaders/vk14/imgui.frag new file mode 100644 index 00000000..e2fa43ed --- /dev/null +++ b/shaders/vk14/imgui.frag @@ -0,0 +1,18 @@ +#version 460 core +#define STAGE_FRAGMENT 1 +// the user interface (imgui): the colour of the vertices times the texture of the draw (the font, an image, a texture), pushed +// with the draw + +#include "common.glsl" + +layout(location = 0) in vec2 f_uv; +layout(location = 1) in vec4 f_colour; + +layout(set = UI_SET, binding = UI_TEXTURE) uniform sampler2D ui_texture; + +layout(location = 0) out vec4 out_colour; + +void main() +{ + out_colour = FBOUT(f_colour * texture(ui_texture, f_uv)); +} diff --git a/shaders/vk14/imgui.vert b/shaders/vk14/imgui.vert new file mode 100644 index 00000000..e8824620 --- /dev/null +++ b/shaders/vk14/imgui.vert @@ -0,0 +1,27 @@ +#version 460 core +#define STAGE_VERTEX 1 +// the user interface (imgui), drawn by vk14::imgui_backend over the image of the window + +#include "common.glsl" + +layout(location = 0) in vec2 v_position; +layout(location = 1) in vec2 v_uv; +layout(location = 2) in vec4 v_colour; + +// the pixels of the window to the clip space +layout(push_constant) uniform ui_transform +{ + vec2 scale; + vec2 translate; +} transform; + +layout(location = 0) out vec2 f_uv; +layout(location = 1) out vec4 f_colour; + +void main() +{ + f_uv = v_uv; + // the colours of imgui are srgb; they are mixed in linear light, as the user interface of the opengl renderer does it + f_colour = vec4(pow(v_colour.rgb, vec3(2.2)), v_colour.a); + gl_Position = vec4(v_position * transform.scale + transform.translate, 0.0, 1.0); +} diff --git a/shaders/vk14bin/README.md b/shaders/vk14bin/README.md new file mode 100644 index 00000000..d9b09990 --- /dev/null +++ b/shaders/vk14bin/README.md @@ -0,0 +1 @@ +SPIR-V binaries of `shaders/vk14/`, made by `shaders/compile_spirv.py` (see `shaders/README.md`). diff --git a/utilities/Globals.cpp b/utilities/Globals.cpp index 0660e2ee..a5596974 100644 --- a/utilities/Globals.cpp +++ b/utilities/Globals.cpp @@ -1711,6 +1711,11 @@ global_settings::ConfigParse_gfx( cParser &Parser, std::string_view const Token Parser.getTokens(1); Parser >> gfx_shadergamma; } + else if (Token == "gfx.shaders.binaries") + { + Parser.getTokens(1); + Parser >> gfx_shader_binaries; + } else if (Token == "gfx.drawrange.factor.max") { Parser.getTokens(1); @@ -2181,6 +2186,7 @@ global_settings::export_as_text( std::ostream &Output ) const { export_as_text( Output, "gfx.skippipeline", gfx_skippipeline ); export_as_text( Output, "gfx.extraeffects", gfx_extraeffects ); export_as_text( Output, "gfx.shadergamma", gfx_shadergamma ); + export_as_text( Output, "gfx.shaders.binaries", gfx_shader_binaries ); export_as_text( Output, "gfx.vulkan.device", gfx_vulkan_device ); export_as_text( Output, "gfx.vulkan.validation", gfx_vulkan_validation ); export_as_text( Output, "gfx.vulkan.framesinflight", gfx_vulkan_framesinflight ); diff --git a/utilities/Globals.h b/utilities/Globals.h index a25408dd..b8fecfbb 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -367,6 +367,8 @@ struct global_settings { bool gfx_extraeffects = true; bool gfx_shadergamma = false; bool gfx_gldebug = false; + // the shaders are loaded from the SPIR-V binaries of shaders/ogl46bin and shaders/vk14bin where there are up to date ones + bool gfx_shader_binaries = true; // (vulkan) the gpu to run on: its index in the list of the log or a part of its name; empty: the best one std::string gfx_vulkan_device; // (vulkan) 0: no validation layer, 1: validation layer, 2: also gpu-assisted validation diff --git a/widgets/cameraview_extcam.cpp b/widgets/cameraview_extcam.cpp index 0b7c1c41..bd2e59a4 100644 --- a/widgets/cameraview_extcam.cpp +++ b/widgets/cameraview_extcam.cpp @@ -8,6 +8,7 @@ #include "utilities/Globals.h" #include "utilities/translation.h" #include "extras/piped_proc.h" +#include "rendering/renderer.h" ui::cameraview_panel::cameraview_panel() : ui_panel(STR_C("Camera preview"), false) @@ -27,6 +28,9 @@ ui::cameraview_panel::~cameraview_panel() { capture_state = STOPPING; if (capture_workthread.joinable()) capture_workthread.join(); + + if ((m_image != 0) && (GfxRenderer != nullptr) && (GfxRenderer->GetImguiRenderer() != nullptr)) + GfxRenderer->GetImguiRenderer()->Release_Image(m_image); } void ui::cameraview_panel::render() @@ -81,38 +85,34 @@ bool ui::cameraview_panel::set_state(bool rec) void ui::cameraview_panel::render_contents() { - if (!texture) { - texture.emplace(); - texture->make_stub(); + auto *renderer{GfxRenderer->GetImguiRenderer()}; + imgui_renderer::image_options options; + options.mipmaps = Global.python_mipmaps; + if ((m_image == 0) && (renderer != nullptr)) { + // grey until the first frame comes + std::uint8_t const stub[2 * 2 * 4] = {0xc0, 0xc0, 0xc0, 0xff, 0xc0, 0xc0, 0xc0, 0xff, 0xc0, 0xc0, 0xc0, 0xff, 0xc0, 0xc0, 0xc0, 0xff}; + m_image = renderer->Create_Image(stub, 2, 2, options); } - texture->bind(0); - { std::scoped_lock lock(mutex); - if (image_ptr) { - - glActiveTexture(GL_TEXTURE0); - - glTexImage2D( - GL_TEXTURE_2D, 0, - GL_SRGB8_ALPHA8, - Global.extcam_res.x, Global.extcam_res.y, 0, - GL_RGB, GL_UNSIGNED_BYTE, image_ptr); + if (image_ptr && (renderer != nullptr)) { + // the frames come as rgb + auto const pixels{static_cast(Global.extcam_res.x) * Global.extcam_res.y}; + m_rgba.resize(pixels * 4); + for (std::size_t i = 0; i < pixels; ++i) { + m_rgba[i * 4 + 0] = image_ptr[i * 3 + 0]; + m_rgba[i * 4 + 1] = image_ptr[i * 3 + 1]; + m_rgba[i * 4 + 2] = image_ptr[i * 3 + 2]; + m_rgba[i * 4 + 3] = 0xff; + } image_ptr = nullptr; - - if (Global.python_mipmaps) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glGenerateMipmap(GL_TEXTURE_2D); - } - else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - } + m_image = renderer->Update_Image(m_image, m_rgba.data(), Global.extcam_res.x, Global.extcam_res.y, options); } } ImVec2 surface_size = ImGui::GetContentRegionAvail(); - ImGui::Image((ImTextureID)(intptr_t)(texture->id), surface_size); + ImGui::Image((ImTextureID)(intptr_t)(m_image), surface_size); } void ui::cameraview_panel::capture_func() diff --git a/widgets/cameraview_extcam.h b/widgets/cameraview_extcam.h index 520fcdda..0bb0a214 100644 --- a/widgets/cameraview_extcam.h +++ b/widgets/cameraview_extcam.h @@ -34,7 +34,9 @@ private: bool recording = false; - std::optional texture; + // the image shown, made by the renderer of the user interface (imgui_renderer), and the frame turned to rgba for it + std::uint64_t m_image{0}; + std::vector m_rgba; void capture_func(); void record_func(); diff --git a/widgets/map.cpp b/widgets/map.cpp index 2340a437..d750bbb6 100644 --- a/widgets/map.cpp +++ b/widgets/map.cpp @@ -16,9 +16,10 @@ ui::map_panel::map_panel() : ui_panel(STR_C("Map"), false) window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse; #ifdef WITH_OPENGL_MODERN + // the map is drawn with the geometry banks and the framebuffers of the opengl renderer opengl46_renderer *renderer = dynamic_cast(GfxRenderer.get()); if (!renderer) { - ErrorLog("map not supported on old renderer"); + WriteLog("map: drawn by the opengl renderer only"); return; } #else