mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-27 10:37:28 +01:00
feat(vulkan): vulkan 1.4 core with the user interface and the launcher, shaders split into ogl46/vk14 with SPIR-V binaries loaded first
This commit is contained in:
11
.github/workflows/shaders.yml
vendored
11
.github/workflows/shaders.yml
vendored
@@ -1,8 +1,9 @@
|
||||
# .github/workflows/shaders.yml
|
||||
# UNTRUSTED CONTEXT. This workflow runs fork-authored code and must not reference `secrets.*`.
|
||||
#
|
||||
# Checks every shader in shaders/ against GLSL 4.60 (compute stage included) with glslang,
|
||||
# after the same preprocessing the game does (ci_shadervalidator). No graphics card is needed.
|
||||
# Checks every shader of shaders/ogl46 against GLSL 4.60 (compute stage included) with glslang, after the same
|
||||
# preprocessing the game does, and as SPIR-V for OpenGL as glslc compiles the binaries of shaders/ogl46bin; the shaders of
|
||||
# shaders/vk14 as SPIR-V for Vulkan (ci_shadervalidator). No graphics card is needed.
|
||||
|
||||
name: Shaders
|
||||
|
||||
@@ -13,6 +14,7 @@ on:
|
||||
paths:
|
||||
- 'shaders/**'
|
||||
- 'gl/**'
|
||||
- 'rendering/common/**'
|
||||
- 'ci_shadervalidator/**'
|
||||
- '.github/workflows/shaders.yml'
|
||||
pull_request:
|
||||
@@ -20,6 +22,7 @@ on:
|
||||
paths:
|
||||
- 'shaders/**'
|
||||
- 'gl/**'
|
||||
- 'rendering/common/**'
|
||||
- 'ci_shadervalidator/**'
|
||||
- '.github/workflows/shaders.yml'
|
||||
|
||||
@@ -27,7 +30,7 @@ permissions: {}
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate shaders (GLSL 4.60)
|
||||
name: Validate shaders (GLSL 4.60, SPIR-V)
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -48,4 +51,4 @@ jobs:
|
||||
|
||||
- name: Validate shaders
|
||||
working-directory: ci_shadervalidator
|
||||
run: ./validateshaders
|
||||
run: ./validateshaders
|
||||
12
.gitmodules
vendored
12
.gitmodules
vendored
@@ -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
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -123,6 +123,8 @@ private:
|
||||
// members
|
||||
|
||||
bool m_screenshot_queued = false;
|
||||
// the ui of the crash gui, drawn in the frames of the renderer instead of the ui of the modes while it's there
|
||||
std::function<void()> m_crashui;
|
||||
|
||||
modeptr_array m_modes { nullptr }; // collection of available application behaviour modes
|
||||
mode_stack m_modestack; // current behaviour mode
|
||||
|
||||
@@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "application/scenarioloaderuilayer.h"
|
||||
|
||||
#include "utilities/Globals.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "utilities/translation.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "utilities/Logs.h"
|
||||
@@ -309,18 +310,13 @@ void scenarioloader_ui::generate_gradient_tex()
|
||||
image_data[(y * image_width + x) * 4 + 3] = std::clamp(static_cast<int>(pow(y / 1023.f, 0.7) * 255), 0, 255);
|
||||
}
|
||||
|
||||
// Create a OpenGL texture identifier
|
||||
GLuint image_texture;
|
||||
glGenTextures(1, &image_texture);
|
||||
glBindTexture(GL_TEXTURE_2D, image_texture);
|
||||
|
||||
// Setup filtering parameters for display
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
|
||||
// Upload pixels into texture
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_width, image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data);
|
||||
// an image of the user interface: the values as they are (not srgb), the nearest texel
|
||||
imgui_renderer::image_options options;
|
||||
options.srgb = false;
|
||||
options.nearest = true;
|
||||
std::uint64_t image_texture{0};
|
||||
if (auto *renderer{GfxRenderer->GetImguiRenderer()})
|
||||
image_texture = renderer->Create_Image(reinterpret_cast<std::uint8_t const *>(image_data), image_width, image_height, options);
|
||||
|
||||
delete[] image_data;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
class scenarioloader_ui : public ui_layer {
|
||||
int m_gradient_overlay_width;
|
||||
int m_gradient_overlay_height;
|
||||
GLuint m_gradient_overlay_tex;
|
||||
std::uint64_t m_gradient_overlay_tex; // imgui_renderer image
|
||||
deferred_image m_loading_wheel_frames[60];
|
||||
std::vector<std::string> m_trivia;
|
||||
public:
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#include "stdafx.h"
|
||||
#include "../gl/shader.h"
|
||||
#include "../gl/glsl_common.h"
|
||||
#include "rendering/common/shadersource.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
@@ -11,99 +12,123 @@
|
||||
|
||||
_global Global;
|
||||
|
||||
bool check_shader(std::string filename, std::string const &defines = {})
|
||||
namespace
|
||||
{
|
||||
gl::shader shader_processor;
|
||||
shader_processor.name = filename;
|
||||
|
||||
gl::glsl_common_setup();
|
||||
std::pair<GLuint, std::string> source = shader_processor.process_source(filename, "../shaders/", defines);
|
||||
std::string const ogl46_dir{"../shaders/ogl46/"};
|
||||
std::string const vk14_dir{"../shaders/vk14/"};
|
||||
// the vulkan environment glslang is asked for: 1.4 where it knows it
|
||||
std::string vulkan_env{"vulkan1.4"};
|
||||
|
||||
std::string stage;
|
||||
if (source.first == GL_VERTEX_SHADER)
|
||||
stage = "vert";
|
||||
else if (source.first == GL_FRAGMENT_SHADER)
|
||||
stage = "frag";
|
||||
else if (source.first == GL_GEOMETRY_SHADER)
|
||||
stage = "geom";
|
||||
else if (source.first == GL_COMPUTE_SHADER)
|
||||
stage = "comp";
|
||||
else
|
||||
return false;
|
||||
char const *stage_name(gfx::shader_stage const Stage)
|
||||
{
|
||||
switch (Stage)
|
||||
{
|
||||
case gfx::shader_stage::vertex:
|
||||
return "vert";
|
||||
case gfx::shader_stage::fragment:
|
||||
return "frag";
|
||||
case gfx::shader_stage::geometry:
|
||||
return "geom";
|
||||
case gfx::shader_stage::compute:
|
||||
return "comp";
|
||||
}
|
||||
return "vert";
|
||||
}
|
||||
|
||||
// Mode: the options of glslangValidator (empty: glsl of opengl, compiled from the source as the game does)
|
||||
bool check_text(gfx::shader_source const &Source, std::string const &Mode)
|
||||
{
|
||||
if (ErrorLogged)
|
||||
{
|
||||
ErrorLogged = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream file("/tmp/shader");
|
||||
file << source.second;
|
||||
file << Source.text;
|
||||
file.close();
|
||||
|
||||
int ret = system(("glslangValidator -S " + stage + " /tmp/shader").c_str());
|
||||
|
||||
// the output only when it fails (glslangValidator names the file it compiled to spir-v)
|
||||
auto const command{"glslangValidator " + Mode + " -S " + stage_name(Source.stage) + " /tmp/shader" + (Mode.empty() ? "" : " -o /tmp/shader.spv") + " > /tmp/shader.log 2>&1"};
|
||||
int ret = system(command.c_str());
|
||||
if (ret != 0)
|
||||
{
|
||||
std::ifstream log("/tmp/shader.log");
|
||||
std::cerr << std::endl << log.rdbuf();
|
||||
std::cerr << "(source strings:";
|
||||
for (std::size_t i = 0; i < Source.files.size(); ++i)
|
||||
std::cerr << " " << i << ": " << Source.files[i];
|
||||
std::cerr << ")" << std::endl;
|
||||
}
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
bool check_shader_allopts(std::string filename)
|
||||
void set_features(bool const Enabled)
|
||||
{
|
||||
Global.gfx_shadowmap_enabled = Enabled;
|
||||
Global.gfx_envmap_enabled = Enabled;
|
||||
Global.gfx_postfx_motionblur_enabled = Enabled;
|
||||
Global.gfx_skippipeline = !Enabled;
|
||||
Global.gfx_extraeffects = Enabled;
|
||||
Global.gfx_shadergamma = Enabled;
|
||||
}
|
||||
|
||||
// a shader of the opengl renderer: compiled from the source as the game does it (with the features on and off, and the variant
|
||||
// of the multi draws), and compiled to SPIR-V for opengl from the file as it is, as glslc makes the binaries of shaders/ogl46bin
|
||||
bool check_ogl46(std::string const &Name)
|
||||
{
|
||||
bool suc = true;
|
||||
auto const stage{gfx::shader_stage_of(Name)};
|
||||
auto const source = [&](std::string const &Defines, std::string const &Preamble) { return gfx::preprocess_shader(gfx::shader_api::opengl46, Name, Defines, Preamble, false, ogl46_dir); };
|
||||
|
||||
std::cout << "features on..." << std::flush;
|
||||
Global.gfx_shadowmap_enabled = true;
|
||||
Global.gfx_envmap_enabled = true;
|
||||
Global.gfx_postfx_motionblur_enabled = true;
|
||||
Global.gfx_skippipeline = false;
|
||||
Global.gfx_extraeffects = true;
|
||||
Global.gfx_shadergamma = true;
|
||||
if (!check_shader(filename))
|
||||
set_features(true);
|
||||
if (!check_text(source({}, gl::glsl_common + gl::glsl_settings_defines()), {}))
|
||||
suc = false;
|
||||
|
||||
std::cout << "done, features off..." << std::flush;
|
||||
Global.gfx_shadowmap_enabled = false;
|
||||
Global.gfx_envmap_enabled = false;
|
||||
Global.gfx_postfx_motionblur_enabled = false;
|
||||
Global.gfx_skippipeline = true;
|
||||
Global.gfx_extraeffects = false;
|
||||
Global.gfx_shadergamma = false;
|
||||
if (!check_shader(filename))
|
||||
set_features(false);
|
||||
if (!check_text(source({}, gl::glsl_common + gl::glsl_settings_defines()), {}))
|
||||
suc = false;
|
||||
|
||||
// the variant of the vertex and fragment stages the multi draws of the instances use (model blocks by gl_DrawID)
|
||||
auto const ext{std::filesystem::path(filename).extension()};
|
||||
if (ext == ".vert" || ext == ".frag")
|
||||
bool const variants{(stage == gfx::shader_stage::vertex) || (stage == gfx::shader_stage::fragment)};
|
||||
if (variants)
|
||||
{
|
||||
std::cout << "done, model draws..." << std::flush;
|
||||
Global.gfx_shadowmap_enabled = true;
|
||||
Global.gfx_envmap_enabled = true;
|
||||
Global.gfx_postfx_motionblur_enabled = true;
|
||||
Global.gfx_skippipeline = false;
|
||||
Global.gfx_extraeffects = true;
|
||||
Global.gfx_shadergamma = true;
|
||||
if (!check_shader(filename, "#define MODEL_DRAWS 1\n"))
|
||||
set_features(true);
|
||||
if (!check_text(source("#define MODEL_DRAWS 1\n", gl::glsl_common + gl::glsl_settings_defines()), {}))
|
||||
suc = false;
|
||||
}
|
||||
|
||||
std::cout << "done" << std::flush;
|
||||
|
||||
return suc;
|
||||
}
|
||||
|
||||
|
||||
bool check_shader_confs(std::string filename)
|
||||
{
|
||||
bool suc = true;
|
||||
|
||||
std::cout << "desktop GL: " << std::flush;
|
||||
if (!check_shader_allopts(filename))
|
||||
std::cout << "done, spir-v..." << std::flush;
|
||||
if (!check_text(source({}, {}), "-G"))
|
||||
suc = false;
|
||||
if (variants && !check_text(source("#define MODEL_DRAWS 1\n", {}), "-G"))
|
||||
suc = false;
|
||||
|
||||
std::cout << std::endl;
|
||||
|
||||
std::cout << "done" << std::flush;
|
||||
return suc;
|
||||
}
|
||||
|
||||
// a shader of the vulkan renderer, compiled to SPIR-V for vulkan as the game does it (and as glslc makes the binaries of
|
||||
// shaders/vk14bin)
|
||||
bool check_vk14(std::string const &Name)
|
||||
{
|
||||
std::cout << "spir-v..." << std::flush;
|
||||
auto const suc{check_text(gfx::preprocess_shader(gfx::shader_api::vulkan14, Name, {}, {}, false, vk14_dir), "-V --target-env " + vulkan_env)};
|
||||
std::cout << "done" << std::flush;
|
||||
return suc;
|
||||
}
|
||||
|
||||
bool is_stage(std::filesystem::path const &Path)
|
||||
{
|
||||
auto const ext = Path.extension();
|
||||
return (ext == ".frag" || ext == ".geom" || ext == ".vert" || ext == ".comp");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
if (system("glslangValidator --version > /dev/null 2>&1") != 0)
|
||||
@@ -111,23 +136,64 @@ int main()
|
||||
std::cerr << "glslangValidator not found in PATH" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
{
|
||||
std::ofstream file("/tmp/shader_env.vert");
|
||||
file << "#version 460\nvoid main() { gl_Position = vec4(0.0); }\n";
|
||||
}
|
||||
if (system("glslangValidator -V --target-env vulkan1.4 /tmp/shader_env.vert -o /tmp/shader_env.spv > /dev/null 2>&1") != 0)
|
||||
{
|
||||
vulkan_env = "vulkan1.3";
|
||||
std::cout << "== glslangValidator doesn't know vulkan 1.4, the vulkan shaders are checked against vulkan 1.3" << std::endl;
|
||||
}
|
||||
|
||||
bool suc = true;
|
||||
std::vector<std::string> failed;
|
||||
for (auto &f : std::filesystem::recursive_directory_iterator("../shaders/")) {
|
||||
if (!f.is_regular_file())
|
||||
continue;
|
||||
|
||||
std::string relative = std::filesystem::relative(f.path(), "../shaders/").string();
|
||||
auto const ext = f.path().extension();
|
||||
if (ext != ".frag" && ext != ".geom" && ext != ".vert" && ext != ".comp")
|
||||
continue;
|
||||
// the binaries are compiled with the constants written in common.glsl, the game compiles the sources with its own
|
||||
gl::glsl_common_setup();
|
||||
std::vector<std::string> differences;
|
||||
std::cout << "== constants of shaders/ogl46/common.glsl: " << std::flush;
|
||||
if (gl::glsl_common_matches(ogl46_dir, differences))
|
||||
{
|
||||
std::cout << "the same as in the game" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "not the same as in the game:" << std::endl;
|
||||
for (auto const &difference : differences)
|
||||
std::cout << " " << difference << std::endl;
|
||||
suc = false;
|
||||
failed.push_back("common.glsl");
|
||||
}
|
||||
|
||||
std::cout << "== validating shader " << relative << ": " << std::flush;
|
||||
if (!check_shader_confs(relative))
|
||||
for (auto const &[directory, api] : {std::pair{ogl46_dir, gfx::shader_api::opengl46}, std::pair{vk14_dir, gfx::shader_api::vulkan14}})
|
||||
{
|
||||
if (false == std::filesystem::is_directory(directory))
|
||||
continue;
|
||||
std::vector<std::string> names;
|
||||
for (auto &f : std::filesystem::directory_iterator(directory))
|
||||
if (f.is_regular_file() && is_stage(f.path()))
|
||||
names.push_back(f.path().filename().string());
|
||||
std::sort(names.begin(), names.end());
|
||||
for (auto const &name : names)
|
||||
{
|
||||
suc = false;
|
||||
failed.push_back(relative);
|
||||
auto const label{std::string(api == gfx::shader_api::opengl46 ? "ogl46/" : "vk14/") + name};
|
||||
std::cout << "== validating shader " << label << ": " << std::flush;
|
||||
bool ok{false};
|
||||
try
|
||||
{
|
||||
ok = (api == gfx::shader_api::opengl46 ? check_ogl46(name) : check_vk14(name));
|
||||
}
|
||||
catch (std::exception const &e)
|
||||
{
|
||||
std::cerr << e.what() << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
if (!ok)
|
||||
{
|
||||
suc = false;
|
||||
failed.push_back(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
#include "stdafx.h"
|
||||
#include "glsl_common.h"
|
||||
#include "rendering/common/shadersource.h"
|
||||
|
||||
std::string gl::glsl_common;
|
||||
|
||||
void gl::glsl_common_setup()
|
||||
{
|
||||
// the values shaders/ogl46/common.glsl takes instead of its own (MASZYNA_CONSTANTS). the settings of the game aren't here,
|
||||
// they are specialization constants of the binaries (glsl_settings_defines(), glsl_settings_values())
|
||||
glsl_common =
|
||||
"#define SHADOWMAP_ENABLED " + std::to_string((int)Global.gfx_shadowmap_enabled) + "\n" +
|
||||
"#define ENVMAP_ENABLED " + std::to_string((int)Global.gfx_envmap_enabled) + "\n" +
|
||||
"#define MOTIONBLUR_ENABLED " + std::to_string((int)Global.gfx_postfx_motionblur_enabled) + "\n" +
|
||||
"#define POSTFX_ENABLED " + std::to_string((int)!Global.gfx_skippipeline) + "\n" +
|
||||
"#define EXTRAEFFECTS_ENABLED " + std::to_string((int)Global.gfx_extraeffects) + "\n" +
|
||||
"#define MASZYNA_CONSTANTS 1\n"
|
||||
"#define MAX_LIGHTS " + std::to_string(MAX_LIGHTS) + "U\n" +
|
||||
"#define MAX_CASCADES " + std::to_string(MAX_CASCADES) + "U\n" +
|
||||
"#define MAX_PARAMS " + std::to_string(MAX_PARAMS) + "U\n" +
|
||||
"#define SHADOW_TEX " + std::to_string(SHADOW_TEX) + "\n" +
|
||||
"#define ENV_TEX " + std::to_string(ENV_TEX) + "\n" +
|
||||
"#define HEADLIGHT_TEX " + std::to_string(HEADLIGHT_TEX) + "\n" +
|
||||
"#define ENV_GGX_TEX " + std::to_string(ENV_GGX_TEX) + "\n" +
|
||||
"#define SCENE_UBO_BINDING " + std::to_string(SCENE_UBO_BINDING) + "\n" +
|
||||
"#define MODEL_UBO_BINDING " + std::to_string(MODEL_UBO_BINDING) + "\n" +
|
||||
"#define INSTANCE_SSBO_BINDING " + std::to_string(INSTANCE_SSBO_BINDING) + "\n" +
|
||||
"#define INSTANCE_RECORD_SSBO_BINDING " + std::to_string(INSTANCE_RECORD_SSBO_BINDING) + "\n" +
|
||||
"#define INSTANCE_JOB_SSBO_BINDING " + std::to_string(INSTANCE_JOB_SSBO_BINDING) + "\n" +
|
||||
@@ -86,223 +91,34 @@ void gl::glsl_common_setup()
|
||||
"#define MAX_CLOUD_LAYERS " + std::to_string(MAX_CLOUD_LAYERS) + "\n" +
|
||||
"#define FOG_HORIZON_SAMPLES " + std::to_string(FOG_HORIZON_SAMPLES) + "\n" +
|
||||
"#define WEATHER_GROUP_SIZE " + std::to_string(WEATHER_GROUP_SIZE) + "\n" +
|
||||
"#define FOG_VOLUME_BIAS " + std::to_string(FOG_VOLUME_BIAS) + "\n" +
|
||||
R"STRING(
|
||||
const uint LIGHT_SPOT = 0U;
|
||||
const uint LIGHT_POINT = 1U;
|
||||
const uint LIGHT_DIR = 2U;
|
||||
const uint LIGHT_HEADLIGHTS = 3U;
|
||||
|
||||
struct light_s
|
||||
{
|
||||
vec3 pos;
|
||||
uint type;
|
||||
|
||||
vec3 dir;
|
||||
float in_cutoff;
|
||||
|
||||
vec3 color;
|
||||
float out_cutoff;
|
||||
|
||||
float linear;
|
||||
float quadratic;
|
||||
|
||||
float intensity;
|
||||
float ambient;
|
||||
|
||||
mat4 headlight_projection;
|
||||
vec4 headlight_weights;
|
||||
};
|
||||
|
||||
// storage blocks are guaranteed only in fragment and compute shaders (GL 4.6 minimum for the other stages is 0)
|
||||
#if defined(STAGE_FRAGMENT) || defined(STAGE_COMPUTE)
|
||||
layout(std430, binding = LIGHT_SSBO_BINDING) readonly buffer light_ssbo
|
||||
{
|
||||
vec3 ambient;
|
||||
// lights stored in lights[], what the cluster culling goes through
|
||||
uint lights_total;
|
||||
|
||||
vec3 fog_color;
|
||||
// lights the shading loop goes through when the lights are not clustered
|
||||
uint lights_count;
|
||||
// 1 if the shading takes the lights from the list of the cluster of a fragment, see cluster_ubo
|
||||
uint lights_clustered;
|
||||
// scale of the directional part of the sky ambient light, see sky_ssbo
|
||||
float sky_ambient_factor;
|
||||
// 1 if rough reflections come from the prefiltered environment map
|
||||
uint env_ggx;
|
||||
|
||||
light_s lights[];
|
||||
};
|
||||
|
||||
// directional part of the sky ambient light: real spherical harmonics (bands 1 and 2, band 0 is ambient) of the light
|
||||
// a surface facing a world space direction gets, in units of ambient. see gl::sky_data
|
||||
layout(std430, binding = SKY_SSBO_BINDING) readonly buffer sky_ssbo
|
||||
{
|
||||
vec4 sky_ambient_sh[9];
|
||||
};
|
||||
#endif
|
||||
|
||||
// light clusters, see gl/cluster_grid.h
|
||||
struct cluster_aabb_s
|
||||
{
|
||||
vec4 lower;
|
||||
vec4 upper;
|
||||
};
|
||||
|
||||
struct cluster_range_s
|
||||
{
|
||||
uint offset;
|
||||
uint count;
|
||||
};
|
||||
|
||||
#if defined(STAGE_FRAGMENT) || defined(STAGE_COMPUTE)
|
||||
// parameters of the grid for the pass being drawn
|
||||
layout(std140, binding = CLUSTER_UBO_BINDING) uniform cluster_ubo
|
||||
{
|
||||
mat4 cluster_projection;
|
||||
float cluster_znear;
|
||||
float cluster_zfar;
|
||||
float cluster_slice_scale;
|
||||
float cluster_slice_bias;
|
||||
vec2 cluster_viewport_size;
|
||||
uint cluster_valid;
|
||||
};
|
||||
|
||||
// the compute passes fill the cluster buffers, shading only reads them
|
||||
#ifdef STAGE_COMPUTE
|
||||
#define CLUSTER_SSBO_ACCESS
|
||||
layout(std430, binding = CLUSTER_AABB_SSBO_BINDING) buffer cluster_aabb_ssbo
|
||||
{
|
||||
cluster_aabb_s cluster_aabbs[];
|
||||
};
|
||||
|
||||
layout(std430, binding = CLUSTER_COUNTER_SSBO_BINDING) buffer cluster_counter_ssbo
|
||||
{
|
||||
uint cluster_index_count;
|
||||
uint cluster_overflow_count;
|
||||
};
|
||||
#else
|
||||
#define CLUSTER_SSBO_ACCESS readonly
|
||||
#endif
|
||||
|
||||
layout(std430, binding = CLUSTER_RANGE_SSBO_BINDING) CLUSTER_SSBO_ACCESS buffer cluster_range_ssbo
|
||||
{
|
||||
cluster_range_s cluster_ranges[];
|
||||
};
|
||||
|
||||
layout(std430, binding = CLUSTER_INDEX_SSBO_BINDING) CLUSTER_SSBO_ACCESS buffer cluster_index_ssbo
|
||||
{
|
||||
uint cluster_light_indices[];
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef MODEL_DRAWS
|
||||
// programs of the multi draws of the instances drawn from the gpu culling: the model block of every draw comes from a
|
||||
// storage buffer, picked by the number of the draw in the multi draw. the fragment stage gets the number from the vertex
|
||||
// stage (which writes f_model_draw), and the names of the uniform block below stand for the fields of the draw
|
||||
struct model_draw_s
|
||||
{
|
||||
mat4 modelview;
|
||||
mat3 modelviewnormal;
|
||||
vec4 param[MAX_PARAMS];
|
||||
|
||||
mat4 future;
|
||||
float opacity;
|
||||
float emission;
|
||||
float fog_density;
|
||||
float alpha_mult;
|
||||
float shadow_tone;
|
||||
};
|
||||
|
||||
layout(std430, binding = MODEL_DRAW_SSBO_BINDING) readonly buffer model_draw_ssbo
|
||||
{
|
||||
model_draw_s model_draws[];
|
||||
};
|
||||
|
||||
#if defined(STAGE_VERTEX)
|
||||
flat out uint f_model_draw;
|
||||
#define MODEL_DRAW gl_DrawID
|
||||
#elif defined(STAGE_FRAGMENT)
|
||||
flat in uint f_model_draw;
|
||||
#define MODEL_DRAW f_model_draw
|
||||
#else
|
||||
#define MODEL_DRAW 0
|
||||
#endif
|
||||
|
||||
#define modelview model_draws[MODEL_DRAW].modelview
|
||||
#define modelviewnormal model_draws[MODEL_DRAW].modelviewnormal
|
||||
#define param model_draws[MODEL_DRAW].param
|
||||
#define future model_draws[MODEL_DRAW].future
|
||||
#define opacity model_draws[MODEL_DRAW].opacity
|
||||
#define emission model_draws[MODEL_DRAW].emission
|
||||
#define fog_density model_draws[MODEL_DRAW].fog_density
|
||||
#define alpha_mult model_draws[MODEL_DRAW].alpha_mult
|
||||
#define shadow_tone model_draws[MODEL_DRAW].shadow_tone
|
||||
#else
|
||||
layout (std140) uniform model_ubo
|
||||
{
|
||||
mat4 modelview;
|
||||
mat3 modelviewnormal;
|
||||
vec4 param[MAX_PARAMS];
|
||||
|
||||
mat4 future;
|
||||
float opacity;
|
||||
float emission;
|
||||
float fog_density;
|
||||
float alpha_mult;
|
||||
float shadow_tone;
|
||||
};
|
||||
#endif
|
||||
|
||||
layout (std140) uniform scene_ubo
|
||||
{
|
||||
mat4 projection;
|
||||
mat4 inv_view;
|
||||
mat4 lightview[MAX_CASCADES];
|
||||
vec3 cascade_end;
|
||||
float time;
|
||||
vec4 rain_params;
|
||||
vec4 wiper_pos;
|
||||
vec4 wiper_timer_out;
|
||||
vec4 wiper_timer_return;
|
||||
// shadow maps of the headlights, see gl::scene_ubs
|
||||
mat4 headlight_shadow[MAX_HEADLIGHT_SHADOWS];
|
||||
vec4 headlight_shadow_params[MAX_HEADLIGHT_SHADOWS];
|
||||
// contact shadows, see gl::scene_ubs
|
||||
vec4 contact_shadow_params;
|
||||
vec4 contact_shadow_lamps;
|
||||
// cascades of the sun shadows fitted to the depth of the view, see gl::scene_ubs
|
||||
vec4 shadow_fit;
|
||||
};
|
||||
|
||||
#ifdef STAGE_VERTEX
|
||||
// Per-instance modelview matrices for GPU-instanced draws, element 0 is identity.
|
||||
// Draws which aren't instanced have gl_BaseInstance == 0 and gl_InstanceID == 0,
|
||||
// so effective_modelview() reduces to model_ubo.modelview. For instanced draws
|
||||
// the C++ side uploads camera-space root transforms of the instances and draws
|
||||
// them from base instance onwards, with model_ubo.modelview set to the submodel-local
|
||||
// chain (computed from identity, NOT from the camera/instance), so the product
|
||||
// gives the correct final transform per instance per submodel.
|
||||
layout(std430, binding = INSTANCE_SSBO_BINDING) readonly buffer instance_ssbo
|
||||
{
|
||||
mat4 instance_modelview[];
|
||||
};
|
||||
|
||||
mat4 effective_modelview() {
|
||||
return instance_modelview[gl_BaseInstance + gl_InstanceID] * modelview;
|
||||
}
|
||||
mat3 effective_modelviewnormal() {
|
||||
// instance_modelview can include per-instance scale (uniform or per-axis),
|
||||
// so its upper-3x3 is NOT its own inverse-transpose in the non-uniform
|
||||
// case. Compute the correct normal matrix via transpose(inverse(...)).
|
||||
// For pure rotation+translation this still produces the rotation matrix
|
||||
// (one extra mat3 inverse per vertex — modern GPUs handle it cheaply).
|
||||
mat3 instance_mv3 = mat3(instance_modelview[gl_BaseInstance + gl_InstanceID]);
|
||||
mat3 instance_normal = transpose(inverse(instance_mv3));
|
||||
return instance_normal * modelviewnormal;
|
||||
}
|
||||
#endif
|
||||
|
||||
)STRING";
|
||||
"#define FOG_VOLUME_BIAS " + std::to_string(FOG_VOLUME_BIAS) + "\n";
|
||||
}
|
||||
|
||||
std::string gl::glsl_settings_defines()
|
||||
{
|
||||
auto const value = [](bool const Value) { return std::string(Value ? "true" : "false"); };
|
||||
return
|
||||
"#define SHADOWMAP_ENABLED_VALUE " + value(Global.gfx_shadowmap_enabled) + "\n"
|
||||
"#define ENVMAP_ENABLED_VALUE " + value(Global.gfx_envmap_enabled) + "\n"
|
||||
"#define MOTIONBLUR_ENABLED_VALUE " + value(Global.gfx_postfx_motionblur_enabled) + "\n"
|
||||
"#define POSTFX_ENABLED_VALUE " + value(!Global.gfx_skippipeline) + "\n"
|
||||
"#define EXTRAEFFECTS_ENABLED_VALUE " + value(Global.gfx_extraeffects) + "\n"
|
||||
"#define OUTPUT_GAMMA_VALUE " + value(Global.gfx_shadergamma) + "\n";
|
||||
}
|
||||
|
||||
std::array<GLuint, 6> gl::glsl_settings_values()
|
||||
{
|
||||
// in the order of gl::shader_setting
|
||||
return {
|
||||
static_cast<GLuint>(Global.gfx_shadowmap_enabled),
|
||||
static_cast<GLuint>(Global.gfx_envmap_enabled),
|
||||
static_cast<GLuint>(Global.gfx_postfx_motionblur_enabled),
|
||||
static_cast<GLuint>(!Global.gfx_skippipeline),
|
||||
static_cast<GLuint>(Global.gfx_extraeffects),
|
||||
static_cast<GLuint>(Global.gfx_shadergamma) };
|
||||
}
|
||||
|
||||
bool gl::glsl_common_matches(std::string const &Basedir, std::vector<std::string> &Differences)
|
||||
{
|
||||
return gfx::shader_constants_match(gfx::shader_api::opengl46, "common.glsl", glsl_common, Basedir, Differences);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ubo.h"
|
||||
#include "utilities/Globals.h"
|
||||
|
||||
namespace gl
|
||||
{
|
||||
// the constants of the game the shaders take (shaders/ogl46/common.glsl), as #define lines put before the sources
|
||||
extern std::string glsl_common;
|
||||
void glsl_common_setup();
|
||||
// the settings of the game the shaders take (shaders/ogl46/settings.glsl): #define lines of their values for the sources,
|
||||
// and the values of the specialization constants of the binaries, in the order of gl::shader_setting
|
||||
std::string glsl_settings_defines();
|
||||
std::array<GLuint, 6> glsl_settings_values();
|
||||
// whether the constants written in common.glsl in Basedir (shaders/ogl46/ when empty) are those of the game: the SPIR-V
|
||||
// binaries are compiled with the ones of the file. Differences: what isn't the same
|
||||
bool glsl_common_matches(std::string const &Basedir, std::vector<std::string> &Differences);
|
||||
}
|
||||
|
||||
474
gl/shader.cpp
474
gl/shader.cpp
@@ -1,292 +1,232 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include "shader.h"
|
||||
#include "glsl_common.h"
|
||||
#include "utilities/Logs.h"
|
||||
|
||||
inline bool strcend(std::string_view value, std::string_view ending)
|
||||
#include <cstring>
|
||||
|
||||
// the values of the components are those of opengl (material textures take them as the hint of their format)
|
||||
static_assert(static_cast<GLint>(gfx::shader_components::R) == GL_RED);
|
||||
static_assert(static_cast<GLint>(gfx::shader_components::RG) == GL_RG);
|
||||
static_assert(static_cast<GLint>(gfx::shader_components::RGB) == GL_RGB);
|
||||
static_assert(static_cast<GLint>(gfx::shader_components::RGBA) == GL_RGBA);
|
||||
static_assert(static_cast<GLint>(gfx::shader_components::sRGB) == GL_SRGB);
|
||||
static_assert(static_cast<GLint>(gfx::shader_components::sRGB_A) == GL_SRGB_ALPHA);
|
||||
static_assert(gfx::MAX_SHADER_TEXTURES == gl::MAX_TEXTURES);
|
||||
static_assert(gfx::MAX_SHADER_PARAMS == gl::MAX_PARAMS);
|
||||
|
||||
namespace
|
||||
{
|
||||
if (ending.size() > value.size())
|
||||
|
||||
GLenum shader_type(gfx::shader_stage const Stage)
|
||||
{
|
||||
switch (Stage)
|
||||
{
|
||||
case gfx::shader_stage::vertex:
|
||||
return GL_VERTEX_SHADER;
|
||||
case gfx::shader_stage::fragment:
|
||||
return GL_FRAGMENT_SHADER;
|
||||
case gfx::shader_stage::geometry:
|
||||
return GL_GEOMETRY_SHADER;
|
||||
case gfx::shader_stage::compute:
|
||||
return GL_COMPUTE_SHADER;
|
||||
}
|
||||
return GL_VERTEX_SHADER;
|
||||
}
|
||||
|
||||
gfx::shader_source preprocess(std::string const &Name, std::string const &Defines, bool const Allowmissing)
|
||||
{
|
||||
return gfx::preprocess_shader(gfx::shader_api::opengl46, Name, Defines, gl::glsl_common + gl::glsl_settings_defines(), Allowmissing);
|
||||
}
|
||||
|
||||
// the files of the sources named by their numbers, for the messages of the compiler (#line <line> <number>)
|
||||
std::string files_of(gfx::shader_source const &Source)
|
||||
{
|
||||
std::string result;
|
||||
for (std::size_t i = 0; i < Source.files.size(); ++i)
|
||||
result += (i > 0 ? ", " : "") + std::to_string(i) + ": " + Source.files[i];
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string shader_log(GLuint const Shader)
|
||||
{
|
||||
GLint length{0};
|
||||
glGetShaderiv(Shader, GL_INFO_LOG_LENGTH, &length);
|
||||
std::string info(static_cast<std::size_t>(std::max(length, 1)), '\0');
|
||||
glGetShaderInfoLog(Shader, static_cast<GLsizei>(info.size()), nullptr, info.data());
|
||||
info.resize(std::strlen(info.c_str()));
|
||||
return info;
|
||||
}
|
||||
|
||||
std::string program_log(GLuint const Program)
|
||||
{
|
||||
GLint length{0};
|
||||
glGetProgramiv(Program, GL_INFO_LOG_LENGTH, &length);
|
||||
std::string info(static_cast<std::size_t>(std::max(length, 1)), '\0');
|
||||
glGetProgramInfoLog(Program, static_cast<GLsizei>(info.size()), nullptr, info.data());
|
||||
info.resize(std::strlen(info.c_str()));
|
||||
return info;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool gl::shader_binaries_enabled()
|
||||
{
|
||||
// decided once, at the first shader: the settings and the driver don't change while the renderer runs
|
||||
static bool const enabled{[]() {
|
||||
if (false == Global.gfx_shader_binaries)
|
||||
{
|
||||
WriteLog("shaders: compiled from the sources (gfx.shaders.binaries no)", logtype::shader);
|
||||
return false;
|
||||
}
|
||||
if ((false == GLAD_GL_VERSION_4_6) || (glSpecializeShader == nullptr))
|
||||
{
|
||||
WriteLog("shaders: the driver can't load SPIR-V, compiled from the sources", logtype::shader);
|
||||
return false;
|
||||
}
|
||||
std::vector<std::string> differences;
|
||||
if (false == glsl_common_matches({}, differences))
|
||||
{
|
||||
std::string text;
|
||||
for (auto const &difference : differences)
|
||||
text += "\n " + difference;
|
||||
ErrorLog("shaders: the constants in " + gfx::shader_source_dir(gfx::shader_api::opengl46) + "common.glsl aren't those of the game, so the SPIR-V binaries of " +
|
||||
gfx::shader_binary_dir(gfx::shader_api::opengl46) + " can't be used; the shaders are compiled from the sources:" + text,
|
||||
logtype::shader);
|
||||
return false;
|
||||
}
|
||||
WriteLog("shaders: the SPIR-V binaries of " + gfx::shader_binary_dir(gfx::shader_api::opengl46) + " are loaded where there are up to date ones", logtype::shader);
|
||||
return true;
|
||||
}()};
|
||||
return enabled;
|
||||
}
|
||||
|
||||
gl::shader::shader(const std::string &filename, const std::string &defines) : name(filename), m_defines(defines)
|
||||
{
|
||||
auto const source{preprocess(filename, defines, true)};
|
||||
texture_conf = source.texture_conf;
|
||||
param_conf = source.param_conf;
|
||||
m_type = shader_type(source.stage);
|
||||
|
||||
if (shader_binaries_enabled() && load_binary(source))
|
||||
{
|
||||
if (false == source.has_source)
|
||||
ErrorLog("shader " + filename + ": the SPIR-V binary is used without its source, so the textures and the parameters of the materials (#pragma texture, #pragma param) aren't known", logtype::shader);
|
||||
return;
|
||||
}
|
||||
|
||||
if (false == source.has_source)
|
||||
throw shader_exception("cannot read shader: " + gfx::shader_source_dir(gfx::shader_api::opengl46) + filename);
|
||||
**this = compile_source(source);
|
||||
}
|
||||
|
||||
bool gl::shader::load_binary(gfx::shader_source const &Source)
|
||||
{
|
||||
std::string reason;
|
||||
auto const words{gfx::load_shader_binary(Source, reason)};
|
||||
if (false == reason.empty())
|
||||
WriteLog("shader " + name + (Source.variant.empty() ? "" : " (" + Source.variant + ")") + ": " + reason + ", compiled from the source", logtype::shader);
|
||||
if (words.empty())
|
||||
return false;
|
||||
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
|
||||
}
|
||||
|
||||
std::string gl::shader::read_file(const std::string &filename) const
|
||||
{
|
||||
std::stringstream stream;
|
||||
std::ifstream f;
|
||||
|
||||
f.open(filename);
|
||||
stream << f.rdbuf();
|
||||
f.close();
|
||||
|
||||
std::string str = stream.str();
|
||||
if (str.empty())
|
||||
throw shader_exception("cannot read shader: " + filename);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void gl::shader::expand_includes(std::string &str, const std::string &basedir)
|
||||
{
|
||||
size_t start_pos = 0;
|
||||
|
||||
std::string magic = "#include";
|
||||
while ((start_pos = str.find(magic, start_pos)) != std::string::npos)
|
||||
auto const id{glCreateShader(m_type)};
|
||||
glShaderBinary(1, &id, GL_SHADER_BINARY_FORMAT_SPIR_V, words.data(), static_cast<GLsizei>(words.size() * sizeof(std::uint32_t)));
|
||||
// the settings the module has, the others aren't given
|
||||
auto const values{glsl_settings_values()};
|
||||
std::vector<GLuint> indices;
|
||||
std::vector<GLuint> settings;
|
||||
for (auto const id : gfx::spirv_specialization_ids(words))
|
||||
{
|
||||
size_t fp = str.find('<', start_pos);
|
||||
size_t fe = str.find('>', start_pos);
|
||||
if (fp == std::string::npos || fe == std::string::npos)
|
||||
return;
|
||||
|
||||
std::string filename = str.substr(fp + 1, fe - fp - 1);
|
||||
std::string content;
|
||||
if (filename != "common")
|
||||
content = read_file(basedir + filename);
|
||||
else
|
||||
content = glsl_common;
|
||||
|
||||
str.replace(start_pos, fe - start_pos + 1, content);
|
||||
if (id < values.size())
|
||||
{
|
||||
indices.push_back(id);
|
||||
settings.push_back(values[id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
glSpecializeShader(id, "main", static_cast<GLuint>(indices.size()), indices.data(), settings.data());
|
||||
|
||||
std::unordered_map<std::string, gl::shader::components_e> gl::shader::components_mapping =
|
||||
{
|
||||
{ "R", components_e::R },
|
||||
{ "RG", components_e::RG },
|
||||
{ "RGB", components_e::RGB },
|
||||
{ "RGBA", components_e::RGBA },
|
||||
{ "sRGB", components_e::sRGB },
|
||||
{ "sRGB_A", components_e::sRGB_A }
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, gl::shader::defaultparam_e> gl::shader::defaultparams_mapping =
|
||||
{
|
||||
{ "required", defaultparam_e::required },
|
||||
{ "nan", defaultparam_e::nan },
|
||||
{ "zero", defaultparam_e::zero },
|
||||
{ "one", defaultparam_e::one },
|
||||
{ "ambient", defaultparam_e::ambient },
|
||||
{ "diffuse", defaultparam_e::diffuse },
|
||||
{ "specular", defaultparam_e::specular },
|
||||
{ "glossiness", defaultparam_e::glossiness }
|
||||
};
|
||||
|
||||
std::pair<GLuint, std::string> gl::shader::process_source(const std::string &filename, const std::string &basedir, const std::string &defines)
|
||||
{
|
||||
std::string str;
|
||||
|
||||
GLuint type;
|
||||
if (strcend(filename, ".vert"))
|
||||
type = GL_VERTEX_SHADER;
|
||||
else if (strcend(filename, ".frag"))
|
||||
type = GL_FRAGMENT_SHADER;
|
||||
else if (strcend(filename, ".geom"))
|
||||
type = GL_GEOMETRY_SHADER;
|
||||
else if (strcend(filename, ".comp"))
|
||||
type = GL_COMPUTE_SHADER;
|
||||
else
|
||||
throw shader_exception("unknown shader " + filename);
|
||||
|
||||
str += "#version 460 core\n";
|
||||
// expose the shader stage to glsl_common so vertex-only built-ins
|
||||
// (gl_InstanceID, effective_modelview, etc.) can be guarded out of
|
||||
// fragment/geometry compilations.
|
||||
if (type == GL_VERTEX_SHADER) str += "#define STAGE_VERTEX 1\n";
|
||||
else if (type == GL_FRAGMENT_SHADER) str += "#define STAGE_FRAGMENT 1\n";
|
||||
else if (type == GL_GEOMETRY_SHADER) str += "#define STAGE_GEOMETRY 1\n";
|
||||
else if (type == GL_COMPUTE_SHADER) str += "#define STAGE_COMPUTE 1\n";
|
||||
str += "vec4 FBOUT(vec4 x) { return " + (Global.gfx_shadergamma ? std::string("vec4(pow(x.rgb, vec3(1.0 / 2.2)), x.a)") : std::string("x")) + "; }\n";
|
||||
// whether the colour written to the window is encoded by the shader (FBOUT) or left to the sRGB framebuffer (dither.glsl)
|
||||
str += std::string("#define OUTPUT_GAMMA ") + (Global.gfx_shadergamma ? "1" : "0") + "\n";
|
||||
str += defines;
|
||||
|
||||
str += read_file(basedir + filename);
|
||||
|
||||
expand_includes(str, basedir);
|
||||
parse_texture_entries(str);
|
||||
parse_param_entries(str);
|
||||
|
||||
return std::make_pair(type, str);
|
||||
}
|
||||
|
||||
void gl::shader::parse_texture_entries(std::string &str)
|
||||
{
|
||||
size_t start_pos = 0;
|
||||
|
||||
std::string magic = "#texture";
|
||||
while ((start_pos = str.find(magic, start_pos)) != std::string::npos)
|
||||
GLint status{GL_FALSE};
|
||||
glGetShaderiv(id, GL_COMPILE_STATUS, &status);
|
||||
if (status == GL_FALSE)
|
||||
{
|
||||
size_t fp = str.find('(', start_pos);
|
||||
size_t fe = str.find(')', start_pos);
|
||||
if (fp == std::string::npos || fe == std::string::npos)
|
||||
return;
|
||||
|
||||
std::istringstream ss(str.substr(fp + 1, fe - fp - 1));
|
||||
std::string token;
|
||||
|
||||
std::string uniformname;
|
||||
texture_entry conf;
|
||||
|
||||
size_t arg = 0;
|
||||
while (std::getline(ss, token, ','))
|
||||
{
|
||||
std::istringstream token_ss(token);
|
||||
if (arg == 0)
|
||||
token_ss >> uniformname;
|
||||
else if (arg == 1)
|
||||
token_ss >> conf.id;
|
||||
else if (arg == 2)
|
||||
{
|
||||
std::string comp;
|
||||
token_ss >> comp;
|
||||
if (!components_mapping.contains(comp))
|
||||
log_error("unknown components: " + comp);
|
||||
else
|
||||
conf.components = components_mapping[comp];
|
||||
}
|
||||
arg++;
|
||||
}
|
||||
|
||||
if (arg == 3)
|
||||
{
|
||||
if (uniformname.empty())
|
||||
log_error("empty name");
|
||||
else if (conf.id >= gl::MAX_TEXTURES)
|
||||
log_error("invalid texture binding: " + std::to_string(conf.id));
|
||||
else
|
||||
texture_conf.try_emplace(uniformname, conf);
|
||||
}
|
||||
else
|
||||
log_error("invalid argument count to #texture");
|
||||
|
||||
str.erase(start_pos, fe - start_pos + 1);
|
||||
WriteLog("shader " + name + ": the SPIR-V binary can't be loaded (" + shader_log(id) + "), compiled from the source", logtype::shader);
|
||||
glDeleteShader(id);
|
||||
return false;
|
||||
}
|
||||
**this = id;
|
||||
m_binary = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void gl::shader::parse_param_entries(std::string &str)
|
||||
GLuint gl::shader::compile_source(gfx::shader_source const &Source) const
|
||||
{
|
||||
size_t start_pos = 0;
|
||||
const GLchar *cstr = Source.text.c_str();
|
||||
|
||||
std::string magic = "#param";
|
||||
while ((start_pos = str.find(magic, start_pos)) != std::string::npos)
|
||||
{
|
||||
size_t fp = str.find('(', start_pos);
|
||||
size_t fe = str.find(')', start_pos);
|
||||
if (fp == std::string::npos || fe == std::string::npos)
|
||||
return;
|
||||
|
||||
std::istringstream ss(str.substr(fp + 1, fe - fp - 1));
|
||||
std::string token;
|
||||
|
||||
std::string uniformname;
|
||||
param_entry conf;
|
||||
|
||||
size_t arg = 0;
|
||||
while (std::getline(ss, token, ','))
|
||||
{
|
||||
std::istringstream token_ss(token);
|
||||
if (arg == 0)
|
||||
token_ss >> uniformname;
|
||||
else if (arg == 1)
|
||||
token_ss >> conf.location;
|
||||
else if (arg == 2)
|
||||
token_ss >> conf.offset;
|
||||
else if (arg == 3)
|
||||
token_ss >> conf.size;
|
||||
else if (arg == 4)
|
||||
{
|
||||
std::string tok;
|
||||
token_ss >> tok;
|
||||
if (!defaultparams_mapping.contains(tok))
|
||||
log_error("unknown param default: " + tok);
|
||||
conf.defaultparam = defaultparams_mapping[tok];
|
||||
}
|
||||
arg++;
|
||||
}
|
||||
|
||||
if (arg == 5)
|
||||
{
|
||||
if (uniformname.empty())
|
||||
log_error("empty name");
|
||||
else if (conf.location >= gl::MAX_PARAMS)
|
||||
log_error("invalid param binding: " + std::to_string(conf.location));
|
||||
else if (conf.offset > 3)
|
||||
log_error("invalid offset: " + std::to_string(conf.offset));
|
||||
else if (conf.offset + conf.size > 4)
|
||||
log_error("invalid size: " + std::to_string(conf.size));
|
||||
else
|
||||
param_conf.try_emplace(uniformname, conf);
|
||||
}
|
||||
else
|
||||
log_error("invalid argument count to #param");
|
||||
|
||||
str.erase(start_pos, fe - start_pos + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void gl::shader::log_error(const std::string &str) const
|
||||
{
|
||||
ErrorLog("bad shader: " + name + ": " + str, logtype::shader);
|
||||
}
|
||||
|
||||
gl::shader::shader(const std::string &filename, const std::string &defines) : name(filename)
|
||||
{
|
||||
|
||||
auto [shadertype, shadersource] = process_source(filename, "shaders/", defines);
|
||||
|
||||
const GLchar *cstr = shadersource.c_str();
|
||||
|
||||
**this = glCreateShader(shadertype);
|
||||
glShaderSource(*this, 1, &cstr, nullptr);
|
||||
glCompileShader(*this);
|
||||
auto const id{glCreateShader(m_type)};
|
||||
glShaderSource(id, 1, &cstr, nullptr);
|
||||
glCompileShader(id);
|
||||
|
||||
GLint status;
|
||||
glGetShaderiv(*this, GL_COMPILE_STATUS, &status);
|
||||
glGetShaderiv(id, GL_COMPILE_STATUS, &status);
|
||||
if (!status)
|
||||
{
|
||||
std::array<GLchar, 512> info{};
|
||||
glGetShaderInfoLog(*this, static_cast<GLsizei>(info.size()), nullptr, info.data());
|
||||
log_error(std::string(info.data()));
|
||||
|
||||
throw shader_exception("failed to compile " + filename + ": " + std::string(info.data()));
|
||||
auto const info{shader_log(id)};
|
||||
glDeleteShader(id);
|
||||
ErrorLog("bad shader: " + name + ": " + info + "(source strings: " + files_of(Source) + ")", logtype::shader);
|
||||
throw shader_exception("failed to compile " + name + ": " + info);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
GLuint gl::shader::source_object() const
|
||||
{
|
||||
if (false == m_binary)
|
||||
return *this;
|
||||
if (m_source_object == 0)
|
||||
m_source_object = compile_source(preprocess(name, m_defines, false));
|
||||
return m_source_object;
|
||||
}
|
||||
|
||||
gl::shader::~shader()
|
||||
{
|
||||
#ifndef SHADERVALIDATOR_STANDALONE
|
||||
if (m_source_object != 0)
|
||||
glDeleteShader(m_source_object);
|
||||
glDeleteShader(*this);
|
||||
#endif
|
||||
}
|
||||
|
||||
void gl::program::init()
|
||||
{
|
||||
bind();
|
||||
|
||||
// the samplers and the blocks have their bindings in the shaders; the sources which don't say them (older ones) are given
|
||||
// them here. a program linked from SPIR-V doesn't know the names, nothing is found then
|
||||
for (auto const &[texturename, textureentry] : texture_conf)
|
||||
{
|
||||
shader::texture_entry const &e = textureentry;
|
||||
GLuint loc = glGetUniformLocation(*this, texturename.c_str());
|
||||
glUniform1i(loc, static_cast<GLint>(e.id));
|
||||
GLint loc = glGetUniformLocation(*this, texturename.c_str());
|
||||
if (loc >= 0)
|
||||
glUniform1i(loc, static_cast<GLint>(textureentry.id));
|
||||
}
|
||||
|
||||
glUniform1i(glGetUniformLocation(*this, "shadowmap"), gl::SHADOW_TEX);
|
||||
glUniform1i(glGetUniformLocation(*this, "envmap"), gl::ENV_TEX);
|
||||
glUniform1i(glGetUniformLocation(*this, "headlightmap"), gl::HEADLIGHT_TEX);
|
||||
auto const unit = [this](char const *Name, std::size_t const Unit) {
|
||||
GLint loc = glGetUniformLocation(*this, Name);
|
||||
if (loc >= 0)
|
||||
glUniform1i(loc, static_cast<GLint>(Unit));
|
||||
};
|
||||
unit("shadowmap", gl::SHADOW_TEX);
|
||||
unit("envmap", gl::ENV_TEX);
|
||||
unit("headlightmap", gl::HEADLIGHT_TEX);
|
||||
// the prefiltered environment map (envmapping.glsl); without a unit of its own the cube sampler would share unit 0 with
|
||||
// the 2d diffuse texture of the materials, which makes their draws fail (GL_INVALID_OPERATION)
|
||||
glUniform1i(glGetUniformLocation(*this, "envmap_ggx"), gl::ENV_GGX_TEX);
|
||||
unit("envmap_ggx", gl::ENV_GGX_TEX);
|
||||
|
||||
GLuint index;
|
||||
|
||||
if ((index = glGetUniformBlockIndex(*this, "scene_ubo")) != GL_INVALID_INDEX)
|
||||
glUniformBlockBinding(*this, index, 0);
|
||||
glUniformBlockBinding(*this, index, gl::SCENE_UBO_BINDING);
|
||||
|
||||
if ((index = glGetUniformBlockIndex(*this, "model_ubo")) != GL_INVALID_INDEX)
|
||||
glUniformBlockBinding(*this, index, 1);
|
||||
glUniformBlockBinding(*this, index, gl::MODEL_UBO_BINDING);
|
||||
}
|
||||
|
||||
gl::program::program()
|
||||
@@ -307,21 +247,51 @@ void gl::program::attach(const gl::shader &s)
|
||||
texture_conf.try_emplace(texturename, std::move(textureentry));
|
||||
for (auto [paramname, paramentry] : s.param_conf)
|
||||
param_conf.try_emplace(paramname, std::move(paramentry));
|
||||
glAttachShader(*this, *s);
|
||||
m_attached.push_back(&s);
|
||||
}
|
||||
|
||||
void gl::program::link()
|
||||
{
|
||||
glLinkProgram(*this);
|
||||
|
||||
GLint status;
|
||||
glGetProgramiv(*this, GL_LINK_STATUS, &status);
|
||||
if (!status)
|
||||
// a program is linked either from SPIR-V binaries or from sources only: when one of the shaders has no binary, the others are
|
||||
// compiled from the source too
|
||||
auto const allbinary{std::all_of(m_attached.begin(), m_attached.end(), [](shader const *Shader) { return Shader->is_binary(); })};
|
||||
GLint status{GL_FALSE};
|
||||
if (allbinary && (false == m_attached.empty()))
|
||||
{
|
||||
std::array<GLchar, 512> info{};
|
||||
glGetProgramInfoLog(*this, static_cast<GLsizei>(info.size()), nullptr, info.data());
|
||||
throw shader_exception("failed to link program: " + std::string(info.data()));
|
||||
for (auto const *s : m_attached)
|
||||
glAttachShader(*this, *s);
|
||||
glLinkProgram(*this);
|
||||
glGetProgramiv(*this, GL_LINK_STATUS, &status);
|
||||
for (auto const *s : m_attached)
|
||||
glDetachShader(*this, *s);
|
||||
if (status == GL_TRUE)
|
||||
{
|
||||
m_binary = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string names;
|
||||
for (auto const *s : m_attached)
|
||||
names += (names.empty() ? "" : ", ") + s->name;
|
||||
WriteLog("program of " + names + ": the SPIR-V binaries can't be linked (" + program_log(*this) + "), compiled from the sources", logtype::shader);
|
||||
}
|
||||
}
|
||||
if (status == GL_FALSE)
|
||||
{
|
||||
std::vector<GLuint> objects;
|
||||
for (auto const *s : m_attached)
|
||||
objects.push_back(s->source_object());
|
||||
for (auto const object : objects)
|
||||
glAttachShader(*this, object);
|
||||
glLinkProgram(*this);
|
||||
glGetProgramiv(*this, GL_LINK_STATUS, &status);
|
||||
for (auto const object : objects)
|
||||
glDetachShader(*this, object);
|
||||
m_binary = false;
|
||||
}
|
||||
m_attached.clear();
|
||||
if (status == GL_FALSE)
|
||||
throw shader_exception("failed to link program: " + program_log(*this));
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
109
gl/shader.h
109
gl/shader.h
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
@@ -7,82 +8,63 @@
|
||||
|
||||
#include "object.h"
|
||||
#include "bindable.h"
|
||||
#include "rendering/common/shadersource.h"
|
||||
|
||||
namespace gl
|
||||
{
|
||||
class shader_exception : public std::runtime_error
|
||||
using shader_exception = gfx::shader_exception;
|
||||
|
||||
// the settings of the game the shaders take (shaders/ogl46/settings.glsl): the ids of the specialization constants of the
|
||||
// SPIR-V binaries; compiled from the source, the shaders get them as constants (gl::glsl_settings_defines())
|
||||
enum class shader_setting : GLuint
|
||||
{
|
||||
using runtime_error::runtime_error;
|
||||
shadowmap = 0,
|
||||
envmap = 1,
|
||||
motionblur = 2,
|
||||
postfx = 3,
|
||||
extraeffects = 4,
|
||||
output_gamma = 5,
|
||||
count = 6
|
||||
};
|
||||
|
||||
// a shader of shaders/ogl46/: the SPIR-V binary of shaders/ogl46bin/ when there's an up to date one (gfx.shaders.binaries),
|
||||
// compiled from the source otherwise
|
||||
class shader : public object
|
||||
{
|
||||
public:
|
||||
#ifdef SHADERVALIDATOR_STANDALONE
|
||||
shader() = default;
|
||||
#endif
|
||||
// Defines: lines put before the source (after the version), e.g. "#define MODEL_DRAWS 1\n" for a variant of the shader
|
||||
explicit shader(const std::string &filename, const std::string &defines = {});
|
||||
~shader();
|
||||
|
||||
enum class components_e
|
||||
{
|
||||
R = GL_RED,
|
||||
RG = GL_RG,
|
||||
RGB = GL_RGB,
|
||||
RGBA = GL_RGBA,
|
||||
sRGB = GL_SRGB,
|
||||
sRGB_A = GL_SRGB_ALPHA
|
||||
};
|
||||
using components_e = gfx::shader_components;
|
||||
using texture_entry = gfx::shader_texture_entry;
|
||||
using defaultparam_e = gfx::shader_param_default;
|
||||
using param_entry = gfx::shader_param_entry;
|
||||
|
||||
struct texture_entry
|
||||
{
|
||||
size_t id;
|
||||
components_e components;
|
||||
};
|
||||
|
||||
enum class defaultparam_e
|
||||
{
|
||||
required,
|
||||
nan,
|
||||
zero,
|
||||
one,
|
||||
ambient,
|
||||
diffuse,
|
||||
specular,
|
||||
glossiness
|
||||
};
|
||||
|
||||
struct param_entry
|
||||
{
|
||||
size_t location;
|
||||
size_t offset;
|
||||
size_t size;
|
||||
defaultparam_e defaultparam;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, texture_entry> texture_conf;
|
||||
std::unordered_map<std::string, param_entry> param_conf;
|
||||
gfx::shader_texture_conf texture_conf;
|
||||
gfx::shader_param_conf param_conf;
|
||||
std::string name;
|
||||
|
||||
#ifndef SHADERVALIDATOR_STANDALONE
|
||||
// whether the shader was loaded from a SPIR-V binary
|
||||
bool is_binary() const
|
||||
{
|
||||
return m_binary;
|
||||
}
|
||||
// the shader compiled from the source: the shader itself, or for a binary one the same shader compiled from the source
|
||||
// (a program can't be linked from binaries and sources together). throws shader_exception
|
||||
GLuint source_object() const;
|
||||
|
||||
private:
|
||||
#endif
|
||||
std::pair<GLuint, std::string> process_source(const std::string &filename, const std::string &basedir, const std::string &defines = {});
|
||||
bool load_binary(gfx::shader_source const &Source);
|
||||
GLuint compile_source(gfx::shader_source const &Source) const;
|
||||
|
||||
void expand_includes(std::string &str, const std::string &basedir);
|
||||
void parse_texture_entries(std::string &str);
|
||||
void parse_param_entries(std::string &str);
|
||||
|
||||
std::string read_file(const std::string &filename) const;
|
||||
|
||||
static std::unordered_map<std::string, components_e> components_mapping;
|
||||
static std::unordered_map<std::string, defaultparam_e> defaultparams_mapping;
|
||||
|
||||
void log_error(const std::string &str) const;
|
||||
GLenum m_type{0};
|
||||
std::string m_defines;
|
||||
bool m_binary{false};
|
||||
mutable GLuint m_source_object{0};
|
||||
};
|
||||
|
||||
class program : public object, public bindable<program>
|
||||
class program : public gfx::shader_program, public object, public bindable<program>
|
||||
{
|
||||
public:
|
||||
program();
|
||||
@@ -92,16 +74,27 @@ namespace gl
|
||||
using bindable::bind;
|
||||
static void bind(GLuint i);
|
||||
|
||||
// the shaders are linked by link(), which has to come while they are there
|
||||
void attach(const shader &);
|
||||
void link();
|
||||
|
||||
// for a program linked from a compute shader only: binds it and runs the given number of work groups
|
||||
void dispatch(GLuint groups_x, GLuint groups_y = 1, GLuint groups_z = 1);
|
||||
|
||||
std::unordered_map<std::string, shader::texture_entry> texture_conf;
|
||||
std::unordered_map<std::string, shader::param_entry> param_conf;
|
||||
// whether the program was linked from SPIR-V binaries
|
||||
bool is_binary() const
|
||||
{
|
||||
return m_binary;
|
||||
}
|
||||
|
||||
private:
|
||||
void init();
|
||||
|
||||
std::vector<shader const *> m_attached;
|
||||
bool m_binary{false};
|
||||
};
|
||||
|
||||
// whether the shaders are loaded from the SPIR-V binaries where there are some: gfx.shaders.binaries, an opengl 4.6 driver,
|
||||
// and the constants of the game the same as those the binaries were compiled with (gl::glsl_common_matches())
|
||||
bool shader_binaries_enabled();
|
||||
}
|
||||
|
||||
6
gl/ubo.h
6
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;
|
||||
|
||||
@@ -16,9 +16,9 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/translation.h"
|
||||
#include "rendering/renderer.h"
|
||||
|
||||
#include "stb/stb_image.h"
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
@@ -100,8 +100,8 @@ ui::scenerywizard_panel::~scenerywizard_panel()
|
||||
{
|
||||
if (m_request.valid())
|
||||
m_request.wait();
|
||||
if (m_texture != 0)
|
||||
glDeleteTextures(1, &m_texture);
|
||||
if ((m_texture != 0) && (GfxRenderer != nullptr) && (GfxRenderer->GetImguiRenderer() != nullptr))
|
||||
GfxRenderer->GetImguiRenderer()->Release_Image(m_texture);
|
||||
}
|
||||
|
||||
void ui::scenerywizard_panel::render_contents()
|
||||
@@ -245,16 +245,11 @@ void ui::scenerywizard_panel::upload_map()
|
||||
return;
|
||||
}
|
||||
m_map_error.clear();
|
||||
if (m_texture == 0)
|
||||
glGenTextures(1, &m_texture);
|
||||
glBindTexture(GL_TEXTURE_2D, m_texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, result.width, result.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, result.pixels.data());
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
// the map as the service sends it, its values taken as they are (not as srgb), with the linear filtering
|
||||
imgui_renderer::image_options options;
|
||||
options.srgb = false;
|
||||
if (auto *renderer{GfxRenderer->GetImguiRenderer()})
|
||||
m_texture = renderer->Update_Image(m_texture, result.pixels.data(), result.width, result.height, options);
|
||||
m_image_low = result.low;
|
||||
m_image_high = result.high;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ class scenerywizard_panel : public ui_panel
|
||||
bool m_dragging{false};
|
||||
|
||||
std::future<map_result> m_request;
|
||||
unsigned int m_texture{0};
|
||||
std::uint64_t m_texture{0}; // imgui_renderer image
|
||||
glm::dvec2 m_image_low{0.0};
|
||||
glm::dvec2 m_image_high{0.0};
|
||||
std::string m_map_error;
|
||||
|
||||
@@ -43,7 +43,7 @@ struct texture_request {
|
||||
int height { 0 };
|
||||
int mapcount { 0 };
|
||||
int blocksize { 0 };
|
||||
// handed to the workers (opengl_texture::prefetch()), and when: the main thread only
|
||||
// handed to the workers (texture_source::prefetch()), and when: the main thread only
|
||||
bool submitted { false };
|
||||
std::chrono::steady_clock::time_point submittime;
|
||||
bool ahead { false }; // read ahead of the camera, which was at the origin then
|
||||
@@ -138,7 +138,7 @@ private:
|
||||
m_read.notify_all();
|
||||
} }
|
||||
|
||||
// same as the reading part of opengl_texture::load_DDS()
|
||||
// same as the reading part of texture_source::load_DDS()
|
||||
static void read( texture_request &Request ) {
|
||||
std::ifstream file( Request.file, std::ios::binary );
|
||||
if( false == file.is_open() ) {
|
||||
@@ -182,7 +182,7 @@ private:
|
||||
std::array<GLuint, gl::MAX_TEXTURES + gl::HELPER_TEXTURES> opengl_texture::units = { 0 };
|
||||
GLint opengl_texture::m_activeunit = -1;
|
||||
|
||||
std::unordered_map<GLint, int> opengl_texture::precompressed_formats =
|
||||
std::unordered_map<GLint, int> texture_source::precompressed_formats =
|
||||
{
|
||||
{ GL_COMPRESSED_RGBA8_ETC2_EAC, 16 },
|
||||
{ GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, 16 },
|
||||
@@ -257,16 +257,25 @@ std::unordered_map<GLint, std::unordered_map<GLint, GLint>> opengl_texture::mapp
|
||||
{ GL_RED, GL_R8 } } },
|
||||
};
|
||||
|
||||
texture_manager::texture_manager() {
|
||||
texture_bank::texture_bank( std::function<texture_source *()> Make ) :
|
||||
m_make( std::move( Make ) ) {
|
||||
|
||||
// since index 0 is used to indicate no texture, we put a blank entry in the first texture slot
|
||||
m_textures.emplace_back( new opengl_texture(), std::chrono::steady_clock::time_point() );
|
||||
m_textures.emplace_back( m_make(), std::chrono::steady_clock::time_point() );
|
||||
}
|
||||
|
||||
texture_bank::~texture_bank() {
|
||||
|
||||
delete_textures();
|
||||
}
|
||||
|
||||
texture_manager::texture_manager() :
|
||||
texture_bank( []() -> texture_source * { return new opengl_texture(); } ) {}
|
||||
|
||||
// loads texture data from specified file
|
||||
// TODO: wrap it in a workitem class, for the job system deferred loading
|
||||
void
|
||||
opengl_texture::load() {
|
||||
texture_source::load() {
|
||||
if (data_state == resource_state::good)
|
||||
return;
|
||||
|
||||
@@ -319,11 +328,11 @@ fail:
|
||||
data_state = resource_state::failed;
|
||||
ErrorLog( "Bad texture: failed to load texture \"" + name + "\"" );
|
||||
// NOTE: temporary workaround for texture assignment errors
|
||||
id = 0;
|
||||
on_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
void opengl_texture::load_PNG()
|
||||
void texture_source::load_PNG()
|
||||
{
|
||||
//png_image png;
|
||||
//memset(&png, 0, sizeof(png_image));
|
||||
@@ -370,7 +379,7 @@ void opengl_texture::load_PNG()
|
||||
//data_state = resource_state::good;
|
||||
}
|
||||
|
||||
void opengl_texture::load_STBI()
|
||||
void texture_source::load_STBI()
|
||||
{
|
||||
int x, y, n;
|
||||
stbi_set_flip_vertically_on_load(1);
|
||||
@@ -395,7 +404,7 @@ void opengl_texture::load_STBI()
|
||||
}
|
||||
|
||||
void
|
||||
opengl_texture::make_stub()
|
||||
texture_source::make_stub()
|
||||
{
|
||||
data_width = 2;
|
||||
data_height = 2;
|
||||
@@ -410,7 +419,7 @@ opengl_texture::make_stub()
|
||||
}
|
||||
|
||||
void
|
||||
opengl_texture::make_from_memory(size_t width, size_t height, const uint8_t *raw)
|
||||
texture_source::make_from_memory(size_t width, size_t height, const uint8_t *raw)
|
||||
{
|
||||
release();
|
||||
|
||||
@@ -467,7 +476,7 @@ void opengl_texture::update_from_memory(size_t width, size_t height, const uint8
|
||||
}
|
||||
|
||||
void
|
||||
opengl_texture::make_request() {
|
||||
texture_source::make_request() {
|
||||
|
||||
auto const components { Split( name, '?' ) };
|
||||
|
||||
@@ -479,7 +488,7 @@ opengl_texture::make_request() {
|
||||
Application.request( { ToLower( components.front() ), dictionary, rt } );
|
||||
}
|
||||
|
||||
DDCOLORKEY opengl_texture::deserialize_ddck(std::istream &s)
|
||||
DDCOLORKEY texture_source::deserialize_ddck(std::istream &s)
|
||||
{
|
||||
DDCOLORKEY ddck;
|
||||
|
||||
@@ -489,7 +498,7 @@ DDCOLORKEY opengl_texture::deserialize_ddck(std::istream &s)
|
||||
return ddck;
|
||||
}
|
||||
|
||||
DDPIXELFORMAT opengl_texture::deserialize_ddpf(std::istream &s)
|
||||
DDPIXELFORMAT texture_source::deserialize_ddpf(std::istream &s)
|
||||
{
|
||||
DDPIXELFORMAT ddpf;
|
||||
|
||||
@@ -505,7 +514,7 @@ DDPIXELFORMAT opengl_texture::deserialize_ddpf(std::istream &s)
|
||||
return ddpf;
|
||||
}
|
||||
|
||||
DDSCAPS2 opengl_texture::deserialize_ddscaps(std::istream &s)
|
||||
DDSCAPS2 texture_source::deserialize_ddscaps(std::istream &s)
|
||||
{
|
||||
DDSCAPS2 ddsc;
|
||||
|
||||
@@ -517,7 +526,7 @@ DDSCAPS2 opengl_texture::deserialize_ddscaps(std::istream &s)
|
||||
return ddsc;
|
||||
}
|
||||
|
||||
DDSURFACEDESC2 opengl_texture::deserialize_ddsd(std::istream &s)
|
||||
DDSURFACEDESC2 texture_source::deserialize_ddsd(std::istream &s)
|
||||
{
|
||||
DDSURFACEDESC2 ddsd;
|
||||
|
||||
@@ -544,7 +553,7 @@ DDSURFACEDESC2 opengl_texture::deserialize_ddsd(std::istream &s)
|
||||
}
|
||||
|
||||
void
|
||||
opengl_texture::load_DDS() {
|
||||
texture_source::load_DDS() {
|
||||
|
||||
std::ifstream file( name + type, std::ios::binary | std::ios::ate ); file.unsetf( std::ios::skipws );
|
||||
std::size_t filesize = static_cast<size_t>(file.tellg()); // ios::ate already positioned us at the end of the file
|
||||
@@ -689,7 +698,7 @@ opengl_texture::load_DDS() {
|
||||
}
|
||||
|
||||
void
|
||||
opengl_texture::load_KTX() {
|
||||
texture_source::load_KTX() {
|
||||
std::ifstream file( name + type, std::ios::binary | std::ios::ate ); file.unsetf( std::ios::skipws );
|
||||
std::size_t filesize = static_cast<size_t>(file.tellg()); // ios::ate already positioned us at the end of the file
|
||||
file.seekg( 0, std::ios::beg ); // rewind the caret afterwards
|
||||
@@ -749,7 +758,7 @@ opengl_texture::load_KTX() {
|
||||
}
|
||||
|
||||
void
|
||||
opengl_texture::load_TEX() {
|
||||
texture_source::load_TEX() {
|
||||
|
||||
std::ifstream file( name + type, std::ios::binary ); file.unsetf( std::ios::skipws );
|
||||
|
||||
@@ -796,7 +805,7 @@ opengl_texture::load_TEX() {
|
||||
}
|
||||
|
||||
void
|
||||
opengl_texture::load_TGA() {
|
||||
texture_source::load_TGA() {
|
||||
|
||||
std::ifstream file( name + type, std::ios::binary ); file.unsetf( std::ios::skipws );
|
||||
|
||||
@@ -1121,7 +1130,7 @@ opengl_texture::create( bool const Static ) {
|
||||
|
||||
// takes over the data read by a worker thread, waiting for it if needed
|
||||
void
|
||||
opengl_texture::complete_data() {
|
||||
texture_source::complete_data() {
|
||||
|
||||
if( request == nullptr ) { return; }
|
||||
|
||||
@@ -1134,7 +1143,7 @@ opengl_texture::complete_data() {
|
||||
data_state = resource_state::failed;
|
||||
ErrorLog( "Bad texture: failed to load texture \"" + name + "\"" );
|
||||
// NOTE: temporary workaround for texture assignment errors, same as in load()
|
||||
id = 0;
|
||||
on_failed();
|
||||
return;
|
||||
}
|
||||
data.swap( completed->data );
|
||||
@@ -1144,10 +1153,10 @@ opengl_texture::complete_data() {
|
||||
|
||||
// has a worker thread read the data of the texture, if it's waiting for that. Origin: the camera, for data read ahead of it
|
||||
void
|
||||
opengl_texture::prefetch( glm::dvec3 const *Origin ) {
|
||||
texture_source::prefetch( glm::dvec3 const *Origin ) {
|
||||
|
||||
if( ( request == nullptr )
|
||||
&& ( id == invalid_id )
|
||||
&& ( false == resident() )
|
||||
&& ( data_state == resource_state::none )
|
||||
&& ( true == reload_on_use )
|
||||
&& ( true == streamed() ) ) {
|
||||
@@ -1169,7 +1178,7 @@ opengl_texture::prefetch( glm::dvec3 const *Origin ) {
|
||||
// the data of the texture is read from its file by worker threads when it's needed (gfx.textures.streaming), and not kept
|
||||
// after the upload: it can be read again
|
||||
bool
|
||||
opengl_texture::streamed() const {
|
||||
texture_source::streamed() const {
|
||||
|
||||
return ( ( true == Global.gfx_texture_streaming )
|
||||
&& ( type == ".dds" )
|
||||
@@ -1256,7 +1265,7 @@ opengl_texture::alloc_rendertarget( GLint format, GLint components, int width, i
|
||||
}
|
||||
|
||||
void
|
||||
opengl_texture::set_components_hint( GLint hint ) {
|
||||
texture_source::set_components_hint( GLint hint ) {
|
||||
|
||||
components_hint = hint;
|
||||
}
|
||||
@@ -1284,7 +1293,7 @@ opengl_texture::set_filtering() const
|
||||
}
|
||||
|
||||
void
|
||||
opengl_texture::downsize( GLuint const Format ) {
|
||||
texture_source::downsize( GLuint const Format ) {
|
||||
|
||||
while( data_width > Global.CurrentMaxTextureSize || data_height > Global.CurrentMaxTextureSize ) {
|
||||
// scale down the base texture, if it's larger than allowed maximum
|
||||
@@ -1313,7 +1322,7 @@ opengl_texture::downsize( GLuint const Format ) {
|
||||
}
|
||||
|
||||
void
|
||||
opengl_texture::flip_vertical() {
|
||||
texture_source::flip_vertical() {
|
||||
|
||||
auto const swapsize { data_width * 4 };
|
||||
auto destination { data.begin() + ( data_height - 1 ) * swapsize };
|
||||
@@ -1338,7 +1347,7 @@ texture_manager::unit( GLint const Textureunit ) {
|
||||
|
||||
// ustalenie numeru tekstury, wczytanie jeśli jeszcze takiej nie było
|
||||
texture_handle
|
||||
texture_manager::create( std::string Filename, bool const Loadnow, GLint Formathint ) {
|
||||
texture_bank::create( std::string Filename, bool const Loadnow, GLint Formathint ) {
|
||||
|
||||
if( contains( Filename, '|' ) ) {
|
||||
Filename.erase( Filename.find( '|' ) ); // po | może być nazwa kolejnej tekstury
|
||||
@@ -1411,7 +1420,7 @@ texture_manager::create( std::string Filename, bool const Loadnow, GLint Formath
|
||||
}
|
||||
}
|
||||
|
||||
auto texture = new opengl_texture();
|
||||
auto texture = m_make();
|
||||
texture->name = locator.first;
|
||||
texture->type = locator.second;
|
||||
texture->traits = traits;
|
||||
@@ -1424,9 +1433,9 @@ texture_manager::create( std::string Filename, bool const Loadnow, GLint Formath
|
||||
|
||||
if( true == Loadnow ) {
|
||||
|
||||
texture_manager::texture( textureindex ).load();
|
||||
texture_bank::texture( textureindex ).load();
|
||||
#ifndef EU07_DEFERRED_TEXTURE_UPLOAD
|
||||
texture_manager::texture( textureindex ).create();
|
||||
texture_bank::texture( textureindex ).create();
|
||||
// texture creation binds a different texture, force a re-bind on next use
|
||||
m_activetexture = -1;
|
||||
#endif
|
||||
@@ -1446,8 +1455,8 @@ texture_manager::bind( std::size_t const Unit, texture_handle const Texture ) {
|
||||
opengl_texture::unbind(Unit);
|
||||
}
|
||||
|
||||
opengl_texture &
|
||||
texture_manager::mark_as_used(const texture_handle Texture) {
|
||||
texture_source &
|
||||
texture_bank::mark_as_used(const texture_handle Texture) {
|
||||
|
||||
auto &pair = m_textures[ Texture ];
|
||||
pair.second = m_garbagecollector.timestamp();
|
||||
@@ -1455,20 +1464,34 @@ texture_manager::mark_as_used(const texture_handle Texture) {
|
||||
}
|
||||
|
||||
void
|
||||
texture_manager::delete_textures() {
|
||||
texture_bank::delete_textures() {
|
||||
for( auto const &texture : m_textures ) {
|
||||
// usunięcie wszyskich tekstur (bez usuwania struktury)
|
||||
if( texture.first->id > 0
|
||||
&& texture.first->id != -1 ) {
|
||||
::glDeleteTextures( 1, &texture.first->id );
|
||||
}
|
||||
texture.first->destroy();
|
||||
delete texture.first;
|
||||
}
|
||||
m_textures.clear();
|
||||
}
|
||||
|
||||
void
|
||||
opengl_texture::destroy() {
|
||||
|
||||
if( id > 0
|
||||
&& id != -1 ) {
|
||||
::glDeleteTextures( 1, &id );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
opengl_texture::on_failed() {
|
||||
|
||||
// NOTE: temporary workaround for texture assignment errors: the texture counts as made, with no gl texture
|
||||
id = 0;
|
||||
}
|
||||
|
||||
// starts a scan for gl textures used only by vehicles far away
|
||||
void
|
||||
texture_manager::begin_release_scan() {
|
||||
texture_bank::begin_release_scan() {
|
||||
|
||||
m_releasescantime = std::chrono::steady_clock::now();
|
||||
// textures bound from now on are stamped with the time of this scan, the sweep may not be running
|
||||
@@ -1480,7 +1503,7 @@ texture_manager::begin_release_scan() {
|
||||
|
||||
// marks texture of a vehicle as needed if the vehicle is near, otherwise only as one used by vehicles
|
||||
void
|
||||
texture_manager::mark_as_needed( texture_handle const Texture, bool const Near ) {
|
||||
texture_bank::mark_as_needed( texture_handle const Texture, bool const Near ) {
|
||||
|
||||
if( ( Texture <= 0 )
|
||||
|| ( Texture >= static_cast<texture_handle>( m_neededtimes.size() ) ) ) {
|
||||
@@ -1506,7 +1529,7 @@ texture_manager::mark_as_needed( texture_handle const Texture, bool const Near )
|
||||
|
||||
// releases gl textures of vehicles not needed nor drawn for a while
|
||||
void
|
||||
texture_manager::release_unneeded() {
|
||||
texture_bank::release_unneeded() {
|
||||
|
||||
// the delay keeps textures of a vehicle moving back and forth around the release distance from going in and out
|
||||
auto const delay { std::chrono::seconds{ 10 } };
|
||||
@@ -1520,8 +1543,8 @@ texture_manager::release_unneeded() {
|
||||
}
|
||||
auto &record { m_textures[ index ] };
|
||||
auto &texture { *record.first };
|
||||
if( ( texture.id == static_cast<GLuint>( -1 ) )
|
||||
|| ( texture.id == 0 )
|
||||
if( ( false == texture.resident() )
|
||||
|| ( texture.get_id() == 0 )
|
||||
|| ( true == texture.is_static )
|
||||
|| ( true == texture.is_rendertarget )
|
||||
|| ( texture.type == "make:" )
|
||||
@@ -1539,26 +1562,30 @@ texture_manager::release_unneeded() {
|
||||
}
|
||||
if( releasecount > 0 ) {
|
||||
// released gl names can be given to new textures, the unit cache can't trust them
|
||||
for( auto &unit : opengl_texture::units ) {
|
||||
unit = -1;
|
||||
}
|
||||
released();
|
||||
}
|
||||
}
|
||||
|
||||
// performs a resource sweep
|
||||
void
|
||||
texture_manager::update() {
|
||||
texture_bank::update() {
|
||||
|
||||
if( m_garbagecollector.sweep() > 0 ) {
|
||||
for( auto &unit : opengl_texture::units ) {
|
||||
unit = -1;
|
||||
}
|
||||
released();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
texture_manager::released() {
|
||||
|
||||
for( auto &unit : opengl_texture::units ) {
|
||||
unit = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// what the textures keep in memory, for the debug panel
|
||||
texture_manager::memory_data
|
||||
texture_manager::memory() const {
|
||||
texture_bank::memory_data
|
||||
texture_bank::memory() const {
|
||||
|
||||
memory_data result;
|
||||
for( auto const &texture : m_textures ) {
|
||||
@@ -1567,7 +1594,7 @@ texture_manager::memory() const {
|
||||
++result.cpu.count;
|
||||
result.cpu.bytes += memory_of( texture.first->data );
|
||||
}
|
||||
if( texture.first->id != opengl_texture::invalid_id ) {
|
||||
if( true == texture.first->resident() ) {
|
||||
++result.gpu.count;
|
||||
result.gpu.bytes += static_cast<std::uint64_t>( texture.first->size ) * 1024;
|
||||
}
|
||||
@@ -1590,7 +1617,7 @@ texture_manager::memory() const {
|
||||
|
||||
// has the data of a texture read, if it's waiting for that
|
||||
void
|
||||
texture_manager::prefetch( texture_handle const Texture, glm::dvec3 const &Origin ) {
|
||||
texture_bank::prefetch( texture_handle const Texture, glm::dvec3 const &Origin ) {
|
||||
|
||||
if( ( Texture <= 0 )
|
||||
|| ( Texture >= static_cast<texture_handle>( m_textures.size() ) ) ) {
|
||||
@@ -1602,7 +1629,7 @@ texture_manager::prefetch( texture_handle const Texture, glm::dvec3 const &Origi
|
||||
// gives back the data read for textures which nothing drew within the specified time since it was asked for, or read ahead of the
|
||||
// camera which went farther than the specified distance from there since. it's read again when a texture is needed
|
||||
std::size_t
|
||||
texture_manager::drop_read_ahead( std::chrono::steady_clock::duration const Age, glm::dvec3 const &Camera, double const Distance ) {
|
||||
texture_bank::drop_read_ahead( std::chrono::steady_clock::duration const Age, glm::dvec3 const &Camera, double const Distance ) {
|
||||
|
||||
auto const now { std::chrono::steady_clock::now() };
|
||||
std::size_t count { 0 };
|
||||
@@ -1629,7 +1656,7 @@ texture_manager::drop_read_ahead( std::chrono::steady_clock::duration const Age,
|
||||
|
||||
// debug performance string
|
||||
std::string
|
||||
texture_manager::info() const {
|
||||
texture_bank::info() const {
|
||||
|
||||
// TODO: cache this data and update only during resource sweep
|
||||
std::size_t totaltexturecount{ m_textures.size() - 1 };
|
||||
@@ -1668,7 +1695,7 @@ texture_manager::info() const {
|
||||
|
||||
// checks whether specified texture is in the texture bank. returns texture id, or npos.
|
||||
texture_handle
|
||||
texture_manager::find_in_databank( std::string const &Texturename ) const {
|
||||
texture_bank::find_in_databank( std::string const &Texturename ) const {
|
||||
|
||||
std::vector<std::string> const filenames {
|
||||
Global.asCurrentTexturePath + Texturename,
|
||||
@@ -1687,7 +1714,7 @@ texture_manager::find_in_databank( std::string const &Texturename ) const {
|
||||
|
||||
// checks whether specified file exists.
|
||||
std::pair<std::string, std::string>
|
||||
texture_manager::find_on_disk( std::string const &Texturename ) {
|
||||
texture_bank::find_on_disk( std::string const &Texturename ) {
|
||||
|
||||
std::vector<std::string> const filenames {
|
||||
Global.asCurrentTexturePath + Texturename,
|
||||
|
||||
159
model/Texture.h
159
model/Texture.h
@@ -15,40 +15,33 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "winheaders.h"
|
||||
#include "utilities/memorystats.h"
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include "model/ResourceManager.h"
|
||||
#include "gl/ubo.h"
|
||||
#include "interfaces/ITexture.h"
|
||||
|
||||
struct texture_request; // texture data read by a worker thread, with gfx.textures.streaming
|
||||
|
||||
struct opengl_texture : public ITexture {
|
||||
// a texture as its file gives it, whichever api draws it: read and decoded (dds, tga, png, bmp, jpg, ktx, tex), with the mipmaps
|
||||
// the file has, kept within maxtexturesize; with gfx.textures.streaming the data of a dds file is read by worker threads. the
|
||||
// data is laid out as opengl takes it (the bottom row first) and its formats are the values of opengl (GL_RGBA,
|
||||
// GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ...), which the renderers take as they are. a renderer makes its own object of the texture
|
||||
// from the data (create())
|
||||
struct texture_source : public ITexture {
|
||||
static DDSURFACEDESC2 deserialize_ddsd(std::istream&);
|
||||
static DDCOLORKEY deserialize_ddck(std::istream&);
|
||||
static DDPIXELFORMAT deserialize_ddpf(std::istream&);
|
||||
static DDSCAPS2 deserialize_ddscaps(std::istream&);
|
||||
|
||||
// constructors
|
||||
opengl_texture() = default;
|
||||
texture_source() = default;
|
||||
// methods
|
||||
void
|
||||
load();
|
||||
bool
|
||||
bind( size_t unit );
|
||||
static void
|
||||
unbind( size_t unit );
|
||||
virtual bool
|
||||
create( bool const Static = false ) override;
|
||||
// releases resources allocated on the opengl end, storing local copy if requested
|
||||
void
|
||||
release() override;
|
||||
void
|
||||
make_stub() override;
|
||||
void
|
||||
alloc_rendertarget( GLint format, GLint components, int width, int height, int layers = 1, int samples = 1, GLint wrap = GL_CLAMP_TO_EDGE );
|
||||
virtual void
|
||||
set_components_hint( GLint hint ) override;
|
||||
static void
|
||||
reset_unit_cache();
|
||||
virtual int
|
||||
get_width() const override {
|
||||
return data_width; }
|
||||
@@ -57,10 +50,6 @@ struct opengl_texture : public ITexture {
|
||||
get_height() const override {
|
||||
return data_height; }
|
||||
virtual
|
||||
size_t
|
||||
get_id() const override {
|
||||
return id; }
|
||||
virtual
|
||||
bool
|
||||
is_stub() const override {
|
||||
return is_texstub; }
|
||||
@@ -86,11 +75,15 @@ struct opengl_texture : public ITexture {
|
||||
return type; }
|
||||
|
||||
virtual void make_from_memory(size_t width, size_t height, const uint8_t *data) override;
|
||||
virtual void update_from_memory(size_t width, size_t height, const uint8_t *data) override;
|
||||
|
||||
// whether the texture has its object made by the renderer (it may be the failed one, with the id 0)
|
||||
virtual bool
|
||||
resident() const = 0;
|
||||
// the object of the renderer goes, with the bank of the textures
|
||||
virtual void
|
||||
destroy() = 0;
|
||||
|
||||
// members
|
||||
static constexpr GLuint invalid_id { std::numeric_limits<GLuint>::max() }; // id value of texture without GL resource
|
||||
GLuint id{ invalid_id }; // associated GL resource
|
||||
bool has_alpha{ false }; // indicates the texture has alpha channel
|
||||
bool is_ready{ false }; // indicates the texture was processed and is ready for use
|
||||
std::string traits; // requested texture attributes: wrapping modes etc
|
||||
@@ -99,10 +92,6 @@ struct opengl_texture : public ITexture {
|
||||
std::size_t size{ 0 }; // size of the texture data, in kb
|
||||
GLint components_hint = 0; // components that material wants
|
||||
|
||||
GLenum target = GL_TEXTURE_2D;
|
||||
static std::array<GLuint, gl::MAX_TEXTURES + gl::HELPER_TEXTURES> units;
|
||||
static GLint m_activeunit;
|
||||
|
||||
public:
|
||||
// methods
|
||||
void make_request();
|
||||
@@ -112,7 +101,6 @@ public:
|
||||
void load_TEX();
|
||||
void load_STBI();
|
||||
void load_TGA();
|
||||
void set_filtering() const;
|
||||
void downsize( GLuint const Format );
|
||||
void flip_vertical();
|
||||
|
||||
@@ -129,8 +117,6 @@ public:
|
||||
bool reload_on_use = false; // the data was dropped along with the gl texture of a far texture, it's read again when the texture is needed
|
||||
bool is_static = false; // is excluded from garbage collection
|
||||
bool is_rendertarget = false; // is used as postfx rendertarget, without loaded data
|
||||
int samples = 1;
|
||||
int layers = 1;
|
||||
bool is_texstub = false; // for make_from_memory internal_src: functionality
|
||||
std::vector<unsigned char> data; // texture data (stored GL-style, bottom-left origin)
|
||||
resource_state data_state{ resource_state::none }; // current state of texture data
|
||||
@@ -140,36 +126,90 @@ public:
|
||||
GLint data_format{ 0 },
|
||||
data_components{ 0 };
|
||||
GLint data_type = GL_UNSIGNED_BYTE;
|
||||
GLint wrap_mode_s = GL_REPEAT;
|
||||
GLint wrap_mode_t = GL_REPEAT;
|
||||
/*
|
||||
std::atomic<bool> is_loaded{ false }; // indicates the texture data was loaded and can be processed
|
||||
std::atomic<bool> is_good{ false }; // indicates the texture data was retrieved without errors
|
||||
*/
|
||||
// the compressed formats the files give, with the bytes of their blocks of 4x4 texels
|
||||
static std::unordered_map<GLint, int> precompressed_formats;
|
||||
static std::unordered_map<GLint, GLint> drivercompressed_formats;
|
||||
static std::unordered_map<GLint, std::unordered_map<GLint, GLint>> mapping;
|
||||
|
||||
protected:
|
||||
// the data of the texture couldn't be read (load(), complete_data())
|
||||
virtual void
|
||||
on_failed() {}
|
||||
};
|
||||
|
||||
class texture_manager {
|
||||
// a texture of the opengl renderer
|
||||
struct opengl_texture : public texture_source {
|
||||
// constructors
|
||||
opengl_texture() = default;
|
||||
// methods
|
||||
bool
|
||||
bind( size_t unit );
|
||||
static void
|
||||
unbind( size_t unit );
|
||||
virtual bool
|
||||
create( bool const Static = false ) override;
|
||||
// releases resources allocated on the opengl end, storing local copy if requested
|
||||
void
|
||||
release() override;
|
||||
void
|
||||
alloc_rendertarget( GLint format, GLint components, int width, int height, int layers = 1, int samples = 1, GLint wrap = GL_CLAMP_TO_EDGE );
|
||||
static void
|
||||
reset_unit_cache();
|
||||
virtual
|
||||
size_t
|
||||
get_id() const override {
|
||||
return id; }
|
||||
virtual void update_from_memory(size_t width, size_t height, const uint8_t *data) override;
|
||||
bool
|
||||
resident() const override {
|
||||
return id != invalid_id; }
|
||||
void
|
||||
destroy() override;
|
||||
|
||||
// members
|
||||
static constexpr GLuint invalid_id { std::numeric_limits<GLuint>::max() }; // id value of texture without GL resource
|
||||
GLuint id{ invalid_id }; // associated GL resource
|
||||
|
||||
GLenum target = GL_TEXTURE_2D;
|
||||
static std::array<GLuint, gl::MAX_TEXTURES + gl::HELPER_TEXTURES> units;
|
||||
static GLint m_activeunit;
|
||||
|
||||
public:
|
||||
texture_manager();
|
||||
~texture_manager() { delete_textures(); }
|
||||
// methods
|
||||
void set_filtering() const;
|
||||
|
||||
// activates specified texture unit
|
||||
// members
|
||||
int samples = 1;
|
||||
int layers = 1;
|
||||
GLint wrap_mode_s = GL_REPEAT;
|
||||
GLint wrap_mode_t = GL_REPEAT;
|
||||
|
||||
static std::unordered_map<GLint, GLint> drivercompressed_formats;
|
||||
static std::unordered_map<GLint, std::unordered_map<GLint, GLint>> mapping;
|
||||
|
||||
protected:
|
||||
void
|
||||
unit( GLint const Textureunit );
|
||||
on_failed() override;
|
||||
};
|
||||
|
||||
// the textures of a renderer: made from their files the first time they are asked for, given back by their handles, released
|
||||
// when nothing used them for a while (gfx.resource.sweep) or when only vehicles far away use them (gfx.textures.releasedistance).
|
||||
// the objects of the textures come from the renderer (the function given to the constructor)
|
||||
class texture_bank {
|
||||
|
||||
public:
|
||||
explicit texture_bank( std::function<texture_source *()> Make );
|
||||
virtual ~texture_bank();
|
||||
|
||||
// creates texture object out of data stored in specified file
|
||||
texture_handle
|
||||
create( std::string Filename, bool const Loadnow = true, GLint Formathint = GL_SRGB_ALPHA );
|
||||
// binds specified texture to specified texture unit
|
||||
void
|
||||
bind( std::size_t const Unit, texture_handle const Texture );
|
||||
opengl_texture &
|
||||
texture_source &
|
||||
mark_as_used( texture_handle const Texture );
|
||||
// provides direct access to specified texture object
|
||||
opengl_texture &
|
||||
texture_source &
|
||||
texture( texture_handle const Texture ) const { return *m_textures[Texture].first; }
|
||||
// performs a resource sweep
|
||||
void
|
||||
@@ -208,10 +248,15 @@ public:
|
||||
std::size_t
|
||||
drop_read_ahead( std::chrono::steady_clock::duration const Age, glm::dvec3 const &Camera, double const Distance );
|
||||
|
||||
protected:
|
||||
// objects of textures were released, the renderer can't trust what it knew of their names
|
||||
virtual void
|
||||
released() {}
|
||||
|
||||
private:
|
||||
// types:
|
||||
typedef std::pair<
|
||||
opengl_texture *,
|
||||
texture_source *,
|
||||
resource_timestamp > texturetimepoint_pair;
|
||||
|
||||
typedef std::vector< texturetimepoint_pair > texturetimepointpair_sequence;
|
||||
@@ -233,6 +278,7 @@ private:
|
||||
delete_textures();
|
||||
|
||||
// members:
|
||||
std::function<texture_source *()> m_make;
|
||||
texture_handle const npos { 0 }; // should be -1, but the rest of the code uses -1 for something else
|
||||
texturetimepointpair_sequence m_textures;
|
||||
index_map m_texturemappings;
|
||||
@@ -241,6 +287,31 @@ private:
|
||||
garbage_collector<texturetimepointpair_sequence> m_garbagecollector { m_textures, 600, 60, "texture" };
|
||||
};
|
||||
|
||||
// the textures of the opengl renderer
|
||||
class texture_manager : public texture_bank {
|
||||
|
||||
public:
|
||||
texture_manager();
|
||||
|
||||
// activates specified texture unit
|
||||
void
|
||||
unit( GLint const Textureunit );
|
||||
// binds specified texture to specified texture unit
|
||||
void
|
||||
bind( std::size_t const Unit, texture_handle const Texture );
|
||||
opengl_texture &
|
||||
mark_as_used( texture_handle const Texture ) {
|
||||
return static_cast<opengl_texture &>( texture_bank::mark_as_used( Texture ) ); }
|
||||
// provides direct access to specified texture object
|
||||
opengl_texture &
|
||||
texture( texture_handle const Texture ) const {
|
||||
return static_cast<opengl_texture &>( texture_bank::texture( Texture ) ); }
|
||||
|
||||
protected:
|
||||
void
|
||||
released() override;
|
||||
};
|
||||
|
||||
// reduces provided data image to half of original size, using basic 2x2 average
|
||||
template <typename Colortype_>
|
||||
void
|
||||
|
||||
@@ -204,7 +204,7 @@ void opengl_material::finalize(bool Loadnow)
|
||||
key.pop_back();
|
||||
if (shader->param_conf.find(key) != shader->param_conf.end())
|
||||
{
|
||||
gl::shader::param_entry entry = shader->param_conf[key];
|
||||
gfx::shader_param_entry entry = shader->param_conf[key];
|
||||
for (size_t i = 0; i < entry.size; i++)
|
||||
params[entry.location][entry.offset + i] = value[i];
|
||||
}
|
||||
@@ -242,17 +242,17 @@ void opengl_material::finalize(bool Loadnow)
|
||||
|
||||
for (auto it : shader->param_conf)
|
||||
{
|
||||
gl::shader::param_entry entry = it.second;
|
||||
gfx::shader_param_entry entry = it.second;
|
||||
if (std::isnan(params[entry.location][entry.offset]))
|
||||
{
|
||||
float value = std::numeric_limits<float>::quiet_NaN();
|
||||
if (entry.defaultparam == gl::shader::defaultparam_e::one)
|
||||
if (entry.defaultparam == gfx::shader_param_default::one)
|
||||
value = 1.0f;
|
||||
else if (entry.defaultparam == gl::shader::defaultparam_e::zero)
|
||||
else if (entry.defaultparam == gfx::shader_param_default::zero)
|
||||
value = 0.0f;
|
||||
else if (entry.defaultparam == gl::shader::defaultparam_e::required)
|
||||
else if (entry.defaultparam == gfx::shader_param_default::required)
|
||||
log_error("unspecified required param: " + it.first);
|
||||
else if (entry.defaultparam != gl::shader::defaultparam_e::nan)
|
||||
else if (entry.defaultparam != gfx::shader_param_default::nan)
|
||||
{
|
||||
params_state.push_back(entry);
|
||||
continue;
|
||||
@@ -265,7 +265,7 @@ void opengl_material::finalize(bool Loadnow)
|
||||
|
||||
for (auto it : shader->texture_conf)
|
||||
{
|
||||
gl::shader::texture_entry &entry = it.second;
|
||||
gfx::shader_texture_entry &entry = it.second;
|
||||
texture_handle handle = textures[entry.id];
|
||||
// NOTE: texture validation at this stage relies on forced texture load behaviour during its create() call
|
||||
// TODO: move texture id validation to later stage if/when deferred texture loading is implemented
|
||||
@@ -302,7 +302,7 @@ bool opengl_material::update() {
|
||||
finalize( true );
|
||||
result = true;
|
||||
}
|
||||
catch( gl::shader_exception const &e ) {
|
||||
catch( gfx::shader_exception const &e ) {
|
||||
ErrorLog( "invalid shader: " + std::string( e.what() ) );
|
||||
}
|
||||
}
|
||||
@@ -434,7 +434,7 @@ opengl_material::deserialize_mapping( cParser &Input, int const Priority, bool c
|
||||
shader = GfxRenderer->Fetch_Shader(value);
|
||||
m_shader_priority = Priority;
|
||||
}
|
||||
catch (gl::shader_exception const &e)
|
||||
catch (gfx::shader_exception const &e)
|
||||
{
|
||||
log_error("invalid shader: " + std::string(e.what()));
|
||||
}
|
||||
@@ -591,7 +591,7 @@ material_manager::create( std::string const &Filename, bool const Loadnow ) {
|
||||
{
|
||||
material.shader = GfxRenderer->Fetch_Shader("default_1");
|
||||
}
|
||||
catch (gl::shader_exception const &e)
|
||||
catch (gfx::shader_exception const &e)
|
||||
{
|
||||
ErrorLog("invalid shader: " + std::string(e.what()));
|
||||
}
|
||||
@@ -625,7 +625,7 @@ material_manager::create( std::string const &Filename, bool const Loadnow ) {
|
||||
materialhandle = m_materials.size();
|
||||
m_materials.emplace_back( std::move(material) );
|
||||
m_materialmappings.emplace( m_materials.back().name, materialhandle );
|
||||
} catch (gl::shader_exception const &e) {
|
||||
} catch (gfx::shader_exception const &e) {
|
||||
ErrorLog("invalid shader: " + std::string(e.what()));
|
||||
// record the failure so subsequent Fetch_Material(filename) calls
|
||||
// short-circuit without re-running the failing compile.
|
||||
@@ -652,7 +652,7 @@ material_manager::create_from_text( std::string const &Name, std::string const &
|
||||
try {
|
||||
material.finalize( true );
|
||||
}
|
||||
catch( gl::shader_exception const &e ) {
|
||||
catch( gfx::shader_exception const &e ) {
|
||||
ErrorLog( "invalid shader: " + std::string( e.what() ) );
|
||||
return null_handle;
|
||||
}
|
||||
@@ -709,7 +709,7 @@ material_manager::create_matte( material_handle const Material ) {
|
||||
matte.params_state.erase(
|
||||
std::remove_if(
|
||||
std::begin( matte.params_state ), std::end( matte.params_state ),
|
||||
[&]( gl::shader::param_entry const &State ) {
|
||||
[&]( gfx::shader_param_entry const &State ) {
|
||||
return State.location == entry->second.location && State.offset == entry->second.offset; } ),
|
||||
std::end( matte.params_state ) );
|
||||
}
|
||||
@@ -747,7 +747,7 @@ material_manager::create_tinted( material_handle const Material, glm::vec3 const
|
||||
tinted.params_state.erase(
|
||||
std::remove_if(
|
||||
std::begin( tinted.params_state ), std::end( tinted.params_state ),
|
||||
[&]( gl::shader::param_entry const &State ) {
|
||||
[&]( gfx::shader_param_entry const &State ) {
|
||||
return State.location == location; } ),
|
||||
std::end( tinted.params_state ) );
|
||||
auto const tintedhandle { static_cast<material_handle>( m_materials.size() ) };
|
||||
@@ -832,7 +832,7 @@ material_manager::blend_lighting( material_handle const Material ) {
|
||||
result = ( fits ? lighting : -1 );
|
||||
}
|
||||
}
|
||||
catch( gl::shader_exception const &e ) {
|
||||
catch( gfx::shader_exception const &e ) {
|
||||
ErrorLog( "invalid shader: " + std::string( e.what() ) );
|
||||
}
|
||||
}
|
||||
@@ -914,7 +914,7 @@ material_manager::build_blend( blend_key const &Key, opengl_material &Blend ) {
|
||||
Blend.finalize( true );
|
||||
return Blend.is_good;
|
||||
}
|
||||
catch( gl::shader_exception const &e ) {
|
||||
catch( gfx::shader_exception const &e ) {
|
||||
ErrorLog( "invalid shader: " + std::string( e.what() ) );
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -12,7 +12,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "interfaces/IMaterial.h"
|
||||
#include "utilities/Classes.h"
|
||||
#include "model/Texture.h"
|
||||
#include "gl/shader.h"
|
||||
#include "rendering/common/shadersource.h"
|
||||
#include "gl/ubo.h"
|
||||
|
||||
// a collection of parameters for the rendering setup.
|
||||
@@ -26,9 +26,9 @@ struct opengl_material : public IMaterial {
|
||||
// NOTE: all variants share the remaining material properties, opacity and translucency included
|
||||
std::vector<texture_set> texture_variants;
|
||||
std::array<glm::vec4, gl::MAX_PARAMS> params;
|
||||
std::vector<gl::shader::param_entry> params_state;
|
||||
std::vector<gfx::shader_param_entry> params_state;
|
||||
|
||||
std::shared_ptr<gl::program> shader;
|
||||
std::shared_ptr<gfx::shader_program> shader;
|
||||
std::optional<float> opacity;
|
||||
std::optional<float> selfillum;
|
||||
float glossiness { 10.f };
|
||||
|
||||
1
ref/Vulkan-Headers
Submodule
1
ref/Vulkan-Headers
Submodule
Submodule ref/Vulkan-Headers added at 6802bb4733
1
ref/VulkanMemoryAllocator
Submodule
1
ref/VulkanMemoryAllocator
Submodule
Submodule ref/VulkanMemoryAllocator added at 1d8f600fd4
1
ref/glslang
Submodule
1
ref/glslang
Submodule
Submodule ref/glslang added at e1b562a8be
1
ref/volk
Submodule
1
ref/volk
Submodule
Submodule ref/volk added at 54fc0d7a72
689
rendering/common/shadersource.cpp
Normal file
689
rendering/common/shadersource.cpp
Normal file
@@ -0,0 +1,689 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/common/shadersource.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
|
||||
#include "utilities/Logs.h"
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool ends_with(std::string_view Value, std::string_view Ending)
|
||||
{
|
||||
return (Ending.size() <= Value.size()) && (Value.compare(Value.size() - Ending.size(), Ending.size(), Ending) == 0);
|
||||
}
|
||||
|
||||
// the content of a file, empty with Found false when it can't be read
|
||||
std::string read_text(std::string const &Path, bool &Found)
|
||||
{
|
||||
std::ifstream file(Path, std::ios::binary);
|
||||
Found = file.is_open();
|
||||
if (false == Found)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
std::stringstream stream;
|
||||
stream << file.rdbuf();
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string read_file(std::string const &Path)
|
||||
{
|
||||
bool found{false};
|
||||
auto text{read_text(Path, found)};
|
||||
if ((false == found) || text.empty())
|
||||
{
|
||||
throw shader_exception("cannot read shader: " + Path);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, shader_components> const components_mapping = {
|
||||
{"R", shader_components::R}, {"RG", shader_components::RG}, {"RGB", shader_components::RGB}, {"RGBA", shader_components::RGBA}, {"sRGB", shader_components::sRGB}, {"sRGB_A", shader_components::sRGB_A}};
|
||||
|
||||
std::unordered_map<std::string, shader_param_default> const defaultparams_mapping = {
|
||||
{"required", shader_param_default::required}, {"nan", shader_param_default::nan}, {"zero", shader_param_default::zero}, {"one", shader_param_default::one},
|
||||
{"ambient", shader_param_default::ambient}, {"diffuse", shader_param_default::diffuse}, {"specular", shader_param_default::specular}, {"glossiness", shader_param_default::glossiness}};
|
||||
|
||||
void log_error(std::string const &Name, std::string const &Message)
|
||||
{
|
||||
ErrorLog("bad shader: " + Name + ": " + Message, logtype::shader);
|
||||
}
|
||||
|
||||
// the position of the next directive Magic ("texture", "param") of the materials: #pragma <magic>(...), or the older #<magic> (...)
|
||||
std::size_t find_directive(std::string const &Text, std::string const &Magic, std::size_t const From, std::size_t &Arguments)
|
||||
{
|
||||
auto start{From};
|
||||
while ((start = Text.find('#', start)) != std::string::npos)
|
||||
{
|
||||
auto position{start + 1};
|
||||
auto const skip_blanks = [&]() {
|
||||
while ((position < Text.size()) && ((Text[position] == ' ') || (Text[position] == '\t')))
|
||||
{
|
||||
++position;
|
||||
}
|
||||
};
|
||||
skip_blanks();
|
||||
if (Text.compare(position, 6, "pragma") == 0)
|
||||
{
|
||||
position += 6;
|
||||
skip_blanks();
|
||||
}
|
||||
if ((Text.compare(position, Magic.size(), Magic) == 0) && (position + Magic.size() < Text.size()))
|
||||
{
|
||||
position += Magic.size();
|
||||
skip_blanks();
|
||||
if ((position < Text.size()) && (Text[position] == '('))
|
||||
{
|
||||
Arguments = position;
|
||||
return start;
|
||||
}
|
||||
}
|
||||
++start;
|
||||
}
|
||||
return std::string::npos;
|
||||
}
|
||||
|
||||
// the arguments of a directive, between the brackets, split at the commas; the directive is taken out of the text
|
||||
std::vector<std::string> take_directive(std::string &Text, std::size_t const Start, std::size_t const Arguments)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
auto const end{Text.find(')', Arguments)};
|
||||
if (end == std::string::npos)
|
||||
{
|
||||
Text.erase(Start, 1);
|
||||
return result;
|
||||
}
|
||||
std::istringstream stream(Text.substr(Arguments + 1, end - Arguments - 1));
|
||||
std::string token;
|
||||
while (std::getline(stream, token, ','))
|
||||
{
|
||||
std::istringstream tokenstream(token);
|
||||
std::string value;
|
||||
tokenstream >> value;
|
||||
result.emplace_back(value);
|
||||
}
|
||||
Text.erase(Start, end - Start + 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
void parse_texture_entries(std::string &Text, shader_source &Source)
|
||||
{
|
||||
std::size_t start{0}, arguments{0};
|
||||
while ((start = find_directive(Text, "texture", start, arguments)) != std::string::npos)
|
||||
{
|
||||
auto const args{take_directive(Text, start, arguments)};
|
||||
if (args.size() != 3)
|
||||
{
|
||||
log_error(Source.name, "invalid argument count to #pragma texture");
|
||||
continue;
|
||||
}
|
||||
shader_texture_entry conf{};
|
||||
try
|
||||
{
|
||||
conf.id = static_cast<std::size_t>(std::stoul(args[1]));
|
||||
}
|
||||
catch (std::exception const &)
|
||||
{
|
||||
log_error(Source.name, "invalid texture binding: " + args[1]);
|
||||
continue;
|
||||
}
|
||||
auto const components{components_mapping.find(args[2])};
|
||||
if (components == components_mapping.end())
|
||||
{
|
||||
log_error(Source.name, "unknown components: " + args[2]);
|
||||
continue;
|
||||
}
|
||||
conf.components = components->second;
|
||||
if (args[0].empty())
|
||||
{
|
||||
log_error(Source.name, "empty name");
|
||||
}
|
||||
else if (conf.id >= MAX_SHADER_TEXTURES)
|
||||
{
|
||||
log_error(Source.name, "invalid texture binding: " + std::to_string(conf.id));
|
||||
}
|
||||
else
|
||||
{
|
||||
Source.texture_conf.try_emplace(args[0], conf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void parse_param_entries(std::string &Text, shader_source &Source)
|
||||
{
|
||||
std::size_t start{0}, arguments{0};
|
||||
while ((start = find_directive(Text, "param", start, arguments)) != std::string::npos)
|
||||
{
|
||||
auto const args{take_directive(Text, start, arguments)};
|
||||
if (args.size() != 5)
|
||||
{
|
||||
log_error(Source.name, "invalid argument count to #pragma param");
|
||||
continue;
|
||||
}
|
||||
shader_param_entry conf{};
|
||||
try
|
||||
{
|
||||
conf.location = static_cast<std::size_t>(std::stoul(args[1]));
|
||||
conf.offset = static_cast<std::size_t>(std::stoul(args[2]));
|
||||
conf.size = static_cast<std::size_t>(std::stoul(args[3]));
|
||||
}
|
||||
catch (std::exception const &)
|
||||
{
|
||||
log_error(Source.name, "invalid param: " + args[0]);
|
||||
continue;
|
||||
}
|
||||
auto const defaultparam{defaultparams_mapping.find(args[4])};
|
||||
if (defaultparam == defaultparams_mapping.end())
|
||||
{
|
||||
log_error(Source.name, "unknown param default: " + args[4]);
|
||||
continue;
|
||||
}
|
||||
conf.defaultparam = defaultparam->second;
|
||||
if (args[0].empty())
|
||||
{
|
||||
log_error(Source.name, "empty name");
|
||||
}
|
||||
else if (conf.location >= MAX_SHADER_PARAMS)
|
||||
{
|
||||
log_error(Source.name, "invalid param binding: " + std::to_string(conf.location));
|
||||
}
|
||||
else if (conf.offset > 3)
|
||||
{
|
||||
log_error(Source.name, "invalid offset: " + std::to_string(conf.offset));
|
||||
}
|
||||
else if (conf.offset + conf.size > 4)
|
||||
{
|
||||
log_error(Source.name, "invalid size: " + std::to_string(conf.size));
|
||||
}
|
||||
else
|
||||
{
|
||||
Source.param_conf.try_emplace(args[0], conf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t count_lines(std::string const &Text, std::size_t const End)
|
||||
{
|
||||
return static_cast<std::size_t>(std::count(Text.begin(), Text.begin() + std::min(End, Text.size()), '\n'));
|
||||
}
|
||||
|
||||
// the number of the file in the list of the files of the source, added when it isn't there
|
||||
std::size_t file_number(shader_source &Source, std::string const &File)
|
||||
{
|
||||
auto const lookup{std::find(Source.files.begin(), Source.files.end(), File)};
|
||||
if (lookup != Source.files.end())
|
||||
{
|
||||
return static_cast<std::size_t>(lookup - Source.files.begin());
|
||||
}
|
||||
Source.files.emplace_back(File);
|
||||
return Source.files.size() - 1;
|
||||
}
|
||||
|
||||
// replaces the includes of Text (the content of the file number Number) with the content of the files they name, with #line
|
||||
// directives which keep the numbers of the lines of each file in the messages of the compiler. an include is a line which
|
||||
// starts with #include (blanks before it allowed)
|
||||
void expand_includes(std::string &Text, std::size_t const Number, std::string const &Basedir, shader_source &Source, int const Depth)
|
||||
{
|
||||
if (Depth > 32)
|
||||
{
|
||||
throw shader_exception("includes nested too deep in " + Source.name);
|
||||
}
|
||||
std::string result;
|
||||
result.reserve(Text.size());
|
||||
std::size_t linestart{0};
|
||||
std::size_t linenumber{1};
|
||||
while (linestart < Text.size())
|
||||
{
|
||||
auto lineend{Text.find('\n', linestart)};
|
||||
if (lineend == std::string::npos)
|
||||
{
|
||||
lineend = Text.size();
|
||||
}
|
||||
std::string_view const line{Text.data() + linestart, lineend - linestart};
|
||||
auto position{line.find_first_not_of(" \t")};
|
||||
bool included{false};
|
||||
if ((position != std::string_view::npos) && (line.compare(position, 8, "#include") == 0))
|
||||
{
|
||||
position = line.find_first_not_of(" \t", position + 8);
|
||||
if (position != std::string_view::npos)
|
||||
{
|
||||
auto const open{line[position]};
|
||||
auto const close{(open == '<') ? '>' : (open == '"') ? '"' : '\0'};
|
||||
auto const end{(close != '\0') ? line.find(close, position + 1) : std::string_view::npos};
|
||||
if (end != std::string_view::npos)
|
||||
{
|
||||
std::string filename{line.substr(position + 1, end - position - 1)};
|
||||
if (filename == "common")
|
||||
{
|
||||
// the name the shaders used for the block of the game before it became a file
|
||||
filename = "common.glsl";
|
||||
}
|
||||
auto const number{file_number(Source, filename)};
|
||||
auto content{read_file(Basedir + filename)};
|
||||
expand_includes(content, number, Basedir, Source, Depth + 1);
|
||||
result += "#line 1 " + std::to_string(number) + "\n";
|
||||
result += content;
|
||||
if ((false == content.empty()) && (content.back() != '\n'))
|
||||
{
|
||||
result += '\n';
|
||||
}
|
||||
result += "#line " + std::to_string(linenumber + 1) + " " + std::to_string(Number);
|
||||
included = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (false == included)
|
||||
{
|
||||
result.append(line);
|
||||
}
|
||||
if (lineend < Text.size())
|
||||
{
|
||||
result += '\n';
|
||||
}
|
||||
linestart = lineend + 1;
|
||||
++linenumber;
|
||||
}
|
||||
Text = std::move(result);
|
||||
}
|
||||
|
||||
std::mutex manifest_mutex;
|
||||
|
||||
// the manifest of the binaries of a directory: binary -> (source file, hash)
|
||||
struct manifest_data
|
||||
{
|
||||
std::filesystem::file_time_type time{};
|
||||
bool found{false};
|
||||
std::unordered_map<std::string, std::vector<std::pair<std::string, std::uint64_t>>> entries;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, manifest_data> manifests;
|
||||
|
||||
manifest_data const &manifest_of(std::string const &Directory)
|
||||
{
|
||||
auto &manifest{manifests[Directory]};
|
||||
auto const path{Directory + "manifest.txt"};
|
||||
std::error_code error;
|
||||
auto const time{std::filesystem::last_write_time(path, error)};
|
||||
if (error)
|
||||
{
|
||||
manifest = manifest_data{};
|
||||
return manifest;
|
||||
}
|
||||
if (manifest.found && (manifest.time == time))
|
||||
{
|
||||
return manifest;
|
||||
}
|
||||
manifest = manifest_data{};
|
||||
manifest.found = true;
|
||||
manifest.time = time;
|
||||
std::ifstream file(path);
|
||||
std::string line;
|
||||
while (std::getline(file, line))
|
||||
{
|
||||
if (line.empty() || (line[0] == '#'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
std::istringstream stream(line);
|
||||
std::string binary, source, hash;
|
||||
if (stream >> binary >> source >> hash)
|
||||
{
|
||||
try
|
||||
{
|
||||
manifest.entries[binary].emplace_back(source, std::stoull(hash, nullptr, 16));
|
||||
}
|
||||
catch (std::exception const &)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
return manifest;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
char const *shader_api_name(shader_api const Api)
|
||||
{
|
||||
return (Api == shader_api::vulkan14 ? "vulkan 1.4" : "opengl 4.6");
|
||||
}
|
||||
|
||||
std::string shader_source_dir(shader_api const Api)
|
||||
{
|
||||
return (Api == shader_api::vulkan14 ? "shaders/vk14/" : "shaders/ogl46/");
|
||||
}
|
||||
|
||||
std::string shader_binary_dir(shader_api const Api)
|
||||
{
|
||||
return (Api == shader_api::vulkan14 ? "shaders/vk14bin/" : "shaders/ogl46bin/");
|
||||
}
|
||||
|
||||
shader_stage shader_stage_of(std::string const &Name)
|
||||
{
|
||||
if (ends_with(Name, ".vert"))
|
||||
{
|
||||
return shader_stage::vertex;
|
||||
}
|
||||
if (ends_with(Name, ".frag"))
|
||||
{
|
||||
return shader_stage::fragment;
|
||||
}
|
||||
if (ends_with(Name, ".geom"))
|
||||
{
|
||||
return shader_stage::geometry;
|
||||
}
|
||||
if (ends_with(Name, ".comp"))
|
||||
{
|
||||
return shader_stage::compute;
|
||||
}
|
||||
throw shader_exception("unknown shader " + Name);
|
||||
}
|
||||
|
||||
char const *shader_stage_define(shader_stage const Stage)
|
||||
{
|
||||
switch (Stage)
|
||||
{
|
||||
case shader_stage::vertex:
|
||||
return "STAGE_VERTEX";
|
||||
case shader_stage::fragment:
|
||||
return "STAGE_FRAGMENT";
|
||||
case shader_stage::geometry:
|
||||
return "STAGE_GEOMETRY";
|
||||
case shader_stage::compute:
|
||||
return "STAGE_COMPUTE";
|
||||
}
|
||||
return "STAGE_VERTEX";
|
||||
}
|
||||
|
||||
std::string shader_binary_name(std::string const &Name, std::string const &Variant)
|
||||
{
|
||||
return Name + (Variant.empty() ? "" : "." + Variant) + ".spv";
|
||||
}
|
||||
|
||||
std::string shader_variant_of(std::string const &Defines, bool &Plain)
|
||||
{
|
||||
Plain = true;
|
||||
std::string result;
|
||||
std::istringstream stream(Defines);
|
||||
std::string line;
|
||||
while (std::getline(stream, line))
|
||||
{
|
||||
std::istringstream linestream(line);
|
||||
std::string directive, name, value, rest;
|
||||
if (false == static_cast<bool>(linestream >> directive))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if ((directive != "#define") || (false == static_cast<bool>(linestream >> name)))
|
||||
{
|
||||
Plain = false;
|
||||
continue;
|
||||
}
|
||||
linestream >> value;
|
||||
if ((false == value.empty()) && (value != "1"))
|
||||
{
|
||||
Plain = false;
|
||||
}
|
||||
if (linestream >> rest)
|
||||
{
|
||||
Plain = false;
|
||||
}
|
||||
result += (result.empty() ? "" : ".") + name;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::uint64_t shader_file_hash(std::string_view const Text)
|
||||
{
|
||||
std::uint64_t hash{14695981039346656037ull};
|
||||
for (std::size_t i = 0; i < Text.size(); ++i)
|
||||
{
|
||||
auto const c{Text[i]};
|
||||
if ((c == '\r') && (i + 1 < Text.size()) && (Text[i + 1] == '\n'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
hash ^= static_cast<std::uint8_t>(c);
|
||||
hash *= 1099511628211ull;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
shader_source preprocess_shader(shader_api const Api, std::string const &Name, std::string const &Defines, std::string const &Preamble, bool const Allowmissing, std::string const &Basedir)
|
||||
{
|
||||
shader_source source;
|
||||
source.api = Api;
|
||||
source.name = Name;
|
||||
source.stage = shader_stage_of(Name);
|
||||
bool plain{true};
|
||||
source.variant = shader_variant_of(Defines, plain);
|
||||
source.has_binary_name = plain;
|
||||
|
||||
auto const basedir{Basedir.empty() ? shader_source_dir(Api) : Basedir};
|
||||
bool found{false};
|
||||
auto text{read_text(basedir + Name, found)};
|
||||
if ((false == found) || text.empty())
|
||||
{
|
||||
if (Allowmissing)
|
||||
{
|
||||
source.files.emplace_back(Name);
|
||||
return source;
|
||||
}
|
||||
throw shader_exception("cannot read shader: " + basedir + Name);
|
||||
}
|
||||
source.has_source = true;
|
||||
source.files.emplace_back(Name);
|
||||
|
||||
// what goes after #version: the variant, the renderer, the stage where the file doesn't say it
|
||||
std::string preamble{Defines};
|
||||
if ((false == preamble.empty()) && (preamble.back() != '\n'))
|
||||
{
|
||||
preamble += '\n';
|
||||
}
|
||||
preamble += Preamble;
|
||||
if ((false == preamble.empty()) && (preamble.back() != '\n'))
|
||||
{
|
||||
preamble += '\n';
|
||||
}
|
||||
if (text.find(std::string("#define ") + shader_stage_define(source.stage)) == std::string::npos)
|
||||
{
|
||||
preamble += std::string("#define ") + shader_stage_define(source.stage) + " 1\n";
|
||||
}
|
||||
|
||||
// the #version line, which has to stay first
|
||||
std::size_t versionend{std::string::npos};
|
||||
{
|
||||
auto version{text.find("#version")};
|
||||
if ((version != std::string::npos) && ((version == 0) || (text[version - 1] == '\n')))
|
||||
{
|
||||
versionend = text.find('\n', version);
|
||||
}
|
||||
}
|
||||
expand_includes(text, 0, basedir, source, 0);
|
||||
parse_texture_entries(text, source);
|
||||
parse_param_entries(text, source);
|
||||
|
||||
if (versionend != std::string::npos)
|
||||
{
|
||||
// the includes come after the #version line, which is where it was
|
||||
versionend = text.find('\n', text.find("#version"));
|
||||
auto const line{count_lines(text, versionend) + 2};
|
||||
text.insert(versionend + 1, preamble + "#line " + std::to_string(line) + " 0\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
text = "#version 460 core\n" + preamble + "#line 1 0\n" + text;
|
||||
}
|
||||
source.text = std::move(text);
|
||||
return source;
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> load_shader_binary(shader_source const &Source, std::string &Reason)
|
||||
{
|
||||
Reason.clear();
|
||||
if (false == Source.has_binary_name)
|
||||
{
|
||||
Reason = "the variant isn't one of plain defines";
|
||||
return {};
|
||||
}
|
||||
auto const directory{shader_binary_dir(Source.api)};
|
||||
auto const name{shader_binary_name(Source.name, Source.variant)};
|
||||
bool found{false};
|
||||
auto const data{read_text(directory + name, found)};
|
||||
if (false == found)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
if ((data.size() < 20) || (data.size() % 4 != 0))
|
||||
{
|
||||
Reason = name + " isn't SPIR-V";
|
||||
return {};
|
||||
}
|
||||
std::vector<std::uint32_t> words(data.size() / 4);
|
||||
std::memcpy(words.data(), data.data(), data.size());
|
||||
if (words[0] != 0x07230203u)
|
||||
{
|
||||
Reason = name + " isn't SPIR-V";
|
||||
return {};
|
||||
}
|
||||
// the sources the binary was compiled from, as the manifest has them: a binary older than its sources isn't taken
|
||||
std::lock_guard<std::mutex> lock(manifest_mutex);
|
||||
auto const &manifest{manifest_of(directory)};
|
||||
auto const entry{manifest.entries.find(name)};
|
||||
if (entry != manifest.entries.end())
|
||||
{
|
||||
auto const sourcedir{shader_source_dir(Source.api)};
|
||||
for (auto const &[file, hash] : entry->second)
|
||||
{
|
||||
bool sourcefound{false};
|
||||
auto const text{read_text(sourcedir + file, sourcefound)};
|
||||
if (false == sourcefound)
|
||||
{
|
||||
// the sources aren't there, so they can't be newer
|
||||
continue;
|
||||
}
|
||||
if (shader_file_hash(text) != hash)
|
||||
{
|
||||
Reason = name + " was compiled from another " + file + " than the one in " + sourcedir;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
return words;
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> spirv_specialization_ids(std::vector<std::uint32_t> const &Words)
|
||||
{
|
||||
std::vector<std::uint32_t> result;
|
||||
// the header is 5 words; each instruction starts with its word count (high 16 bits) and opcode (low 16 bits)
|
||||
std::size_t index{5};
|
||||
while (index < Words.size())
|
||||
{
|
||||
auto const count{Words[index] >> 16};
|
||||
auto const opcode{Words[index] & 0xffffu};
|
||||
if ((count == 0) || (index + count > Words.size()))
|
||||
{
|
||||
break;
|
||||
}
|
||||
// OpDecorate %target SpecId <id>
|
||||
if ((opcode == 71) && (count >= 4) && (Words[index + 2] == 1))
|
||||
{
|
||||
result.push_back(Words[index + 3]);
|
||||
}
|
||||
index += count;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string> shader_file_defines(shader_api const Api, std::string const &Name, std::string const &Basedir)
|
||||
{
|
||||
std::unordered_map<std::string, std::string> result;
|
||||
bool found{false};
|
||||
auto const text{read_text((Basedir.empty() ? shader_source_dir(Api) : Basedir) + Name, found)};
|
||||
std::istringstream stream(text);
|
||||
std::string line;
|
||||
while (std::getline(stream, line))
|
||||
{
|
||||
std::istringstream linestream(line);
|
||||
std::string directive, name, value;
|
||||
if ((linestream >> directive >> name) && (directive == "#define"))
|
||||
{
|
||||
std::getline(linestream, value);
|
||||
auto const first{value.find_first_not_of(" \t")};
|
||||
auto const last{value.find_last_not_of(" \t\r")};
|
||||
result[name] = (first == std::string::npos ? std::string{} : value.substr(first, last - first + 1));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool shader_constants_match(shader_api const Api, std::string const &Name, std::string const &Preamble, std::string const &Basedir, std::vector<std::string> &Differences)
|
||||
{
|
||||
Differences.clear();
|
||||
auto const file{shader_file_defines(Api, Name, Basedir)};
|
||||
if (file.empty())
|
||||
{
|
||||
Differences.emplace_back(Name + " can't be read");
|
||||
return false;
|
||||
}
|
||||
// the same number however it's written: 0.2 and 0.200000, 3 and 3U
|
||||
auto const normalized = [](std::string Value) {
|
||||
if ((false == Value.empty()) && ((Value.back() == 'U') || (Value.back() == 'u')))
|
||||
{
|
||||
Value.pop_back();
|
||||
}
|
||||
try
|
||||
{
|
||||
std::size_t used{0};
|
||||
auto const number{std::stod(Value, &used)};
|
||||
if (used == Value.size())
|
||||
{
|
||||
return std::to_string(number);
|
||||
}
|
||||
}
|
||||
catch (std::exception const &)
|
||||
{
|
||||
}
|
||||
return Value;
|
||||
};
|
||||
std::istringstream stream(Preamble);
|
||||
std::string line;
|
||||
while (std::getline(stream, line))
|
||||
{
|
||||
std::istringstream linestream(line);
|
||||
std::string directive, name, value;
|
||||
if (!(linestream >> directive >> name >> value) || (directive != "#define"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto const lookup{file.find(name)};
|
||||
if (lookup == file.end())
|
||||
{
|
||||
Differences.emplace_back(name + " is missing");
|
||||
}
|
||||
else if (normalized(lookup->second) != normalized(value))
|
||||
{
|
||||
Differences.emplace_back(name + " is " + lookup->second + " in the file and " + value + " in the game");
|
||||
}
|
||||
}
|
||||
return Differences.empty();
|
||||
}
|
||||
|
||||
} // namespace gfx
|
||||
173
rendering/common/shadersource.h
Normal file
173
rendering/common/shadersource.h
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// the shader files of the renderers, whichever api compiles them: where they are, how a file is made ready to compile (its
|
||||
// includes, the directives of the materials, what the renderer puts before the source), and the SPIR-V binaries compiled from
|
||||
// them ahead of time, which the renderers load before they compile the source.
|
||||
//
|
||||
// each api has a directory of sources and one of binaries: shaders/ogl46/ and shaders/ogl46bin/ for opengl 4.6,
|
||||
// shaders/vk14/ and shaders/vk14bin/ for vulkan 1.4. a binary is named after its source, <file>.spv, and a variant of the
|
||||
// shader (the defines of the renderer, e.g. MODEL_DRAWS) <file>.<DEFINE>.spv. the sources can be compiled by glslc as they are
|
||||
// (shaders/compile_spirv.py), which also writes the manifest of the binaries: the sources each binary was compiled from, so a
|
||||
// binary older than its sources isn't taken.
|
||||
//
|
||||
// no graphics api here: the file is also built into the shader validator (ci_shadervalidator)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace gfx
|
||||
{
|
||||
|
||||
class shader_exception : public std::runtime_error
|
||||
{
|
||||
using runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
enum class shader_api
|
||||
{
|
||||
opengl46,
|
||||
vulkan14
|
||||
};
|
||||
|
||||
enum class shader_stage
|
||||
{
|
||||
vertex,
|
||||
fragment,
|
||||
geometry,
|
||||
compute
|
||||
};
|
||||
|
||||
// textures and parameters a material shader can take, the same as gl::MAX_TEXTURES and gl::MAX_PARAMS
|
||||
constexpr std::size_t MAX_SHADER_TEXTURES = 8;
|
||||
constexpr std::size_t MAX_SHADER_PARAMS = 3;
|
||||
|
||||
// what the texture of a unit holds, as #pragma texture(name, unit, components) gives it. the values are those of opengl
|
||||
// (GL_RED ... GL_SRGB_ALPHA), which the textures take as the hint of their format
|
||||
enum class shader_components : int
|
||||
{
|
||||
R = 0x1903,
|
||||
RG = 0x8227,
|
||||
RGB = 0x1907,
|
||||
RGBA = 0x1908,
|
||||
sRGB = 0x8C40,
|
||||
sRGB_A = 0x8C42
|
||||
};
|
||||
|
||||
struct shader_texture_entry
|
||||
{
|
||||
std::size_t id;
|
||||
shader_components components;
|
||||
};
|
||||
|
||||
enum class shader_param_default
|
||||
{
|
||||
required,
|
||||
nan,
|
||||
zero,
|
||||
one,
|
||||
ambient,
|
||||
diffuse,
|
||||
specular,
|
||||
glossiness
|
||||
};
|
||||
|
||||
// #pragma param(name, location, offset, size, default): a part of one of the vec4 parameters of the model block
|
||||
struct shader_param_entry
|
||||
{
|
||||
std::size_t location;
|
||||
std::size_t offset;
|
||||
std::size_t size;
|
||||
shader_param_default defaultparam;
|
||||
};
|
||||
|
||||
using shader_texture_conf = std::unordered_map<std::string, shader_texture_entry>;
|
||||
using shader_param_conf = std::unordered_map<std::string, shader_param_entry>;
|
||||
|
||||
// a program of a material as the materials see it, whichever api draws it: the textures and the parameters it takes, the
|
||||
// name it was fetched by (gfx_renderer::Fetch_Shader)
|
||||
class shader_program
|
||||
{
|
||||
public:
|
||||
virtual ~shader_program() = default;
|
||||
|
||||
shader_texture_conf texture_conf;
|
||||
shader_param_conf param_conf;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
// a shader file made ready to compile
|
||||
struct shader_source
|
||||
{
|
||||
shader_api api{shader_api::opengl46};
|
||||
shader_stage stage{shader_stage::vertex};
|
||||
// the file as asked for, e.g. mat_default.frag
|
||||
std::string name;
|
||||
// the names of the defines of the variant joined with dots (MODEL_DRAWS), empty for the shader itself. a variant whose
|
||||
// defines aren't all plain flags (#define NAME or #define NAME 1) has no binary: invalid_variant
|
||||
std::string variant;
|
||||
bool has_binary_name{true};
|
||||
// whether the source file is there; a binary can be loaded without it, but the material directives come from the source
|
||||
bool has_source{false};
|
||||
// the source with the preamble put after #version and the includes expanded, for a compiler of the source. the files are
|
||||
// told apart in the messages of the compiler by their numbers in files (#line <line> <number>)
|
||||
std::string text;
|
||||
// the file and its includes, relative to the directory of the sources, by their numbers
|
||||
std::vector<std::string> files;
|
||||
shader_texture_conf texture_conf;
|
||||
shader_param_conf param_conf;
|
||||
};
|
||||
|
||||
// "opengl 4.6", "vulkan 1.4"
|
||||
char const *shader_api_name(shader_api Api);
|
||||
// the directories of the sources and of the binaries of an api: shaders/ogl46/, shaders/ogl46bin/ ...
|
||||
std::string shader_source_dir(shader_api Api);
|
||||
std::string shader_binary_dir(shader_api Api);
|
||||
// the stage of a file by its extension (.vert, .frag, .geom, .comp). throws shader_exception for another one
|
||||
shader_stage shader_stage_of(std::string const &Name);
|
||||
// the define the files set for their stage: STAGE_VERTEX ...
|
||||
char const *shader_stage_define(shader_stage Stage);
|
||||
// the file name of the binary of a shader: mat_default.frag.spv, vertex.vert.MODEL_DRAWS.spv
|
||||
std::string shader_binary_name(std::string const &Name, std::string const &Variant);
|
||||
// the variant named by the defines of a renderer ("#define MODEL_DRAWS 1\n" -> MODEL_DRAWS). Plain: false when a define isn't
|
||||
// a plain flag (the variant can't have a binary then)
|
||||
std::string shader_variant_of(std::string const &Defines, bool &Plain);
|
||||
// the hash the manifest of the binaries keeps of a source file: fnv-1a 64 of the text with the line ends made \n
|
||||
std::uint64_t shader_file_hash(std::string_view Text);
|
||||
|
||||
// reads the shader Name of Api and makes it ready to compile: the includes expanded (#include "file", #include <file>; <common>
|
||||
// stands for common.glsl), #pragma texture(...) and #pragma param(...) read and taken out of the text, and after the #version
|
||||
// line (#version 460 core put first when the file has none) the defines of the variant, the preamble of the renderer and,
|
||||
// where the file doesn't set it, the define of its stage. Basedir: the directory of the sources, shader_source_dir() when empty.
|
||||
// a missing file is an error unless Allowmissing (the source then comes back with has_source false). throws shader_exception
|
||||
shader_source preprocess_shader(shader_api Api, std::string const &Name, std::string const &Defines, std::string const &Preamble, bool const Allowmissing = false,
|
||||
std::string const &Basedir = {});
|
||||
|
||||
// the SPIR-V binary of a shader from the directory of the binaries of its api. empty when there's none, when the manifest of the
|
||||
// binaries says the binary was compiled from other sources than those there now, or when the file isn't SPIR-V; Reason says
|
||||
// which (empty when there's simply no binary)
|
||||
std::vector<std::uint32_t> load_shader_binary(shader_source const &Source, std::string &Reason);
|
||||
|
||||
// the ids of the specialization constants of a SPIR-V module (OpDecorate ... SpecId)
|
||||
std::vector<std::uint32_t> spirv_specialization_ids(std::vector<std::uint32_t> const &Words);
|
||||
|
||||
// the #define lines of a source, NAME -> value as written, for the checks of the constants the binaries were compiled with
|
||||
std::unordered_map<std::string, std::string> shader_file_defines(shader_api Api, std::string const &Name, std::string const &Basedir = {});
|
||||
// whether the constants a renderer puts before the sources (the #define lines of Preamble) are those the file Name of Api writes
|
||||
// for itself (the binaries are compiled by glslc with the values of the file). Differences: what isn't the same
|
||||
bool shader_constants_match(shader_api Api, std::string const &Name, std::string const &Preamble, std::string const &Basedir, std::vector<std::string> &Differences);
|
||||
|
||||
} // namespace gfx
|
||||
@@ -107,7 +107,7 @@ public:
|
||||
IMaterial const *
|
||||
Material( material_handle const Material ) const override { return m_materials.at(Material - 1).get(); }
|
||||
// shader methods
|
||||
auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gl::program> override { throw std::runtime_error("not impl"); }
|
||||
auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gfx::shader_program> override { throw std::runtime_error("not impl"); }
|
||||
// texture methods
|
||||
texture_handle
|
||||
Fetch_Texture( std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA ) override { throw std::runtime_error("not impl"); }
|
||||
|
||||
@@ -95,19 +95,19 @@ bool opengl46_grass::init()
|
||||
return false;
|
||||
}
|
||||
m_vao = std::make_unique<gl::vao>();
|
||||
auto const location = [&](char const *Name) { return ::glGetUniformLocation(*m_program, Name); };
|
||||
m_uniforms.tile = location("grass_tile");
|
||||
m_uniforms.part = location("grass_part");
|
||||
m_uniforms.chunk = location("grass_chunk");
|
||||
m_uniforms.camera = location("grass_camera");
|
||||
m_uniforms.lod = location("grass_lod");
|
||||
m_uniforms.wind = location("grass_wind");
|
||||
m_uniforms.base = location("grass_base");
|
||||
m_uniforms.tip = location("grass_tip");
|
||||
m_uniforms.shape = location("grass_shape");
|
||||
m_uniforms.share = location("grass_share");
|
||||
m_uniforms.dry = location("grass_dry");
|
||||
m_uniforms.season = location("grass_season");
|
||||
// the locations grass.vert gives its uniforms (a program loaded from SPIR-V doesn't know their names)
|
||||
m_uniforms.tile = 0;
|
||||
m_uniforms.part = 1;
|
||||
m_uniforms.chunk = 2;
|
||||
m_uniforms.camera = 3;
|
||||
m_uniforms.lod = 4;
|
||||
m_uniforms.wind = 5;
|
||||
m_uniforms.base = 6;
|
||||
m_uniforms.tip = 10;
|
||||
m_uniforms.shape = 14;
|
||||
m_uniforms.share = 18;
|
||||
m_uniforms.dry = 19;
|
||||
m_uniforms.season = 23;
|
||||
m_state = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -200,8 +200,8 @@ bool opengl46_renderer::Init(GLFWwindow *Window)
|
||||
return false;
|
||||
}
|
||||
|
||||
scene_ubo = std::make_unique<gl::ubo>(sizeof(gl::scene_ubs), 0);
|
||||
model_ubo = std::make_unique<gl::stream_ubo>(sizeof(gl::model_ubs), 1, Global.gfx_bufferstreaming);
|
||||
scene_ubo = std::make_unique<gl::ubo>(sizeof(gl::scene_ubs), gl::SCENE_UBO_BINDING);
|
||||
model_ubo = std::make_unique<gl::stream_ubo>(sizeof(gl::model_ubs), gl::MODEL_UBO_BINDING, Global.gfx_bufferstreaming);
|
||||
light_ssbo = std::make_unique<gl::ssbo>(sizeof(gl::light_data), gl::LIGHT_SSBO_BINDING);
|
||||
light_ssbo->bind_base();
|
||||
{
|
||||
@@ -673,6 +673,8 @@ std::unique_ptr<gl::program> opengl46_renderer::make_shader(std::string v, std::
|
||||
|
||||
bool opengl46_renderer::Render()
|
||||
{
|
||||
// the texture names bound before may have been given to other textures since the last frame
|
||||
opengl_texture::reset_unit_cache();
|
||||
Timer::subsystem.gfx_total.start();
|
||||
|
||||
std::optional<int64_t> result = m_timequery->result();
|
||||
@@ -3270,7 +3272,7 @@ bool opengl46_renderer::Blendable_Material(material_handle const Material)
|
||||
return m_materials.blendable(Material);
|
||||
}
|
||||
|
||||
std::string opengl46_renderer::Shader_Name(std::shared_ptr<gl::program> const &Program) const
|
||||
std::string opengl46_renderer::Shader_Name(std::shared_ptr<gfx::shader_program> const &Program) const
|
||||
{
|
||||
for (auto const &shader : m_shaders)
|
||||
if (shader.second == Program)
|
||||
@@ -3278,7 +3280,7 @@ std::string opengl46_renderer::Shader_Name(std::shared_ptr<gl::program> const &P
|
||||
return {};
|
||||
}
|
||||
|
||||
std::shared_ptr<gl::program> opengl46_renderer::Fetch_Shader(const std::string &name)
|
||||
std::shared_ptr<gfx::shader_program> opengl46_renderer::Fetch_Shader(const std::string &name)
|
||||
{
|
||||
auto it = m_shaders.find(name);
|
||||
if (it == m_shaders.end())
|
||||
@@ -3472,7 +3474,7 @@ void opengl46_renderer::Bind_Material( material_handle const Material, TSubModel
|
||||
if (lastdiff)
|
||||
glBindTextures(0, lastdiff, &opengl_texture::units[0]);
|
||||
|
||||
auto *program{material.shader.get()};
|
||||
auto *program{static_cast<gl::program *>(material.shader.get())};
|
||||
if (m_bindmultidraw)
|
||||
{
|
||||
// the replay of the instances drawn from the gpu culling with multi draws (Render_Instances_Indirect_Colour())
|
||||
@@ -4887,7 +4889,7 @@ int opengl46_renderer::Prepass_Class( material_handle const Material )
|
||||
if( Material == null_handle ) { return 0; }
|
||||
auto const &material { m_materials.material( Material ) };
|
||||
if( ( false == material.is_good ) || ( material.shader == nullptr ) ) { return 0; }
|
||||
auto const lookup { m_prepassclasses.find( material.shader.get() ) };
|
||||
auto const lookup { m_prepassclasses.find( static_cast<gl::program const *>( material.shader.get() ) ) };
|
||||
if( lookup != m_prepassclasses.end() ) { return lookup->second; }
|
||||
|
||||
// names the programs are fetched by: mat_<name>.frag
|
||||
@@ -4904,7 +4906,7 @@ int opengl46_renderer::Prepass_Class( material_handle const Material )
|
||||
prepassclass = ( alphatested.count( shader.first ) > 0 ? 2 : solid.count( shader.first ) > 0 ? 1 : 0 );
|
||||
break;
|
||||
}
|
||||
m_prepassclasses.emplace( material.shader.get(), prepassclass );
|
||||
m_prepassclasses.emplace( static_cast<gl::program const *>( material.shader.get() ), prepassclass );
|
||||
return prepassclass;
|
||||
}
|
||||
|
||||
@@ -5678,7 +5680,7 @@ gl::program const *opengl46_renderer::Indirect_Program( material_handle Material
|
||||
material = &m_materials.material( m_invalid_material );
|
||||
if( false == material->is_good ) { return nullptr; }
|
||||
}
|
||||
return material->shader.get();
|
||||
return static_cast<gl::program const *>( material->shader.get() );
|
||||
}
|
||||
|
||||
gl::program *opengl46_renderer::Multidraw_Program( gl::program const *Program )
|
||||
@@ -8867,7 +8869,11 @@ std::string const &opengl46_renderer::info_stats() const
|
||||
|
||||
void opengl46_renderer::MakeScreenshot()
|
||||
{
|
||||
screenshot_manager::make_screenshot();
|
||||
// rows are tightly packed RGB: the renderer sets GL_PACK_ALIGNMENT to 1
|
||||
auto img = new char[Global.fb_size.x * Global.fb_size.y * 3];
|
||||
glReadPixels(0, 0, Global.fb_size.x, Global.fb_size.y, GL_RGB, GL_UNSIGNED_BYTE, (GLvoid*)img);
|
||||
//m7t: use pbo
|
||||
screenshot_manager::save(img, Global.fb_size.x, Global.fb_size.y);
|
||||
}
|
||||
|
||||
// model previews
|
||||
@@ -10239,9 +10245,14 @@ void opengl46_renderer::opengl46_imgui_renderer::Render()
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
}
|
||||
|
||||
std::uint64_t opengl46_renderer::opengl46_imgui_renderer::Create_Image(std::uint8_t const *Rgba, int const Width, int const Height)
|
||||
std::uint64_t opengl46_renderer::opengl46_imgui_renderer::Create_Image(std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
|
||||
{
|
||||
// the ui colours are made linear in its shaders, the image is too, by sampling it as srgb. the bindings the renderer
|
||||
return Update_Image(0, Rgba, Width, Height, Options);
|
||||
}
|
||||
|
||||
std::uint64_t opengl46_renderer::opengl46_imgui_renderer::Update_Image(std::uint64_t const Image, std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
|
||||
{
|
||||
// the ui colours are made linear in its shaders, an srgb image is too, by sampling it as srgb. the bindings the renderer
|
||||
// keeps track of are put back as they were
|
||||
GLint texture{0}, unpack{0}, alignment{4};
|
||||
glGetIntegerv(GL_TEXTURE_BINDING_2D, &texture);
|
||||
@@ -10249,14 +10260,18 @@ std::uint64_t opengl46_renderer::opengl46_imgui_renderer::Create_Image(std::uint
|
||||
glGetIntegerv(GL_UNPACK_ALIGNMENT, &alignment);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
GLuint image{0};
|
||||
glGenTextures(1, &image);
|
||||
auto image{static_cast<GLuint>(Image)};
|
||||
if (image == 0)
|
||||
glGenTextures(1, &image);
|
||||
glBindTexture(GL_TEXTURE_2D, image);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
auto const filter{Options.nearest ? GL_NEAREST : GL_LINEAR};
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, Options.mipmaps ? (Options.nearest ? GL_NEAREST_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR) : filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Rgba);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, Options.srgb ? GL_SRGB8_ALPHA8 : GL_RGBA8, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, Rgba);
|
||||
if (Options.mipmaps)
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, static_cast<GLuint>(texture));
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast<GLuint>(unpack));
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
|
||||
|
||||
@@ -117,8 +117,8 @@ class opengl46_renderer : public gfx_renderer {
|
||||
void
|
||||
Terrain_Grass( std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks ) override;
|
||||
// shader methods
|
||||
auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gl::program> override;
|
||||
auto Shader_Name( std::shared_ptr<gl::program> const &Program ) const -> std::string override;
|
||||
auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gfx::shader_program> override;
|
||||
auto Shader_Name( std::shared_ptr<gfx::shader_program> const &Program ) const -> std::string override;
|
||||
// texture methods
|
||||
texture_handle
|
||||
Fetch_Texture( std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA ) override;
|
||||
@@ -1178,7 +1178,9 @@ class opengl46_renderer : public gfx_renderer {
|
||||
virtual void Shutdown() override;
|
||||
virtual void BeginFrame() override;
|
||||
virtual void Render() override;
|
||||
virtual std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height) override;
|
||||
using imgui_renderer::Create_Image;
|
||||
virtual std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options) override;
|
||||
virtual std::uint64_t Update_Image(std::uint64_t const Image, std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options) override;
|
||||
virtual void Release_Image(std::uint64_t const Image) override;
|
||||
} m_imgui_renderer;
|
||||
|
||||
|
||||
@@ -92,8 +92,8 @@ class opengl46_terrain_materials
|
||||
std::vector<slot> m_slots;
|
||||
std::vector<std::size_t> m_free;
|
||||
std::unordered_map<material_handle, std::size_t> m_slotof;
|
||||
std::shared_ptr<gl::program> m_program; // of the chunks, mat_terrain
|
||||
std::shared_ptr<gl::program> m_holesprogram; // of the chunks with holes, mat_terrain_holes
|
||||
std::shared_ptr<gfx::shader_program> m_program; // of the chunks, mat_terrain
|
||||
std::shared_ptr<gfx::shader_program> m_holesprogram; // of the chunks with holes, mat_terrain_holes
|
||||
std::unique_ptr<gl::postfx> m_copy;
|
||||
std::unique_ptr<gl::framebuffer> m_framebuffer;
|
||||
};
|
||||
|
||||
@@ -19,9 +19,9 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
struct lighting_data;
|
||||
|
||||
namespace gl
|
||||
namespace gfx
|
||||
{
|
||||
class program;
|
||||
class shader_program;
|
||||
}
|
||||
|
||||
namespace gfx
|
||||
@@ -132,9 +132,9 @@ public:
|
||||
// again. chunks left out are dropped. backends which can't draw grass ignore it
|
||||
virtual void Terrain_Grass( std::vector<gfx::grass_kind> const &Kinds, std::vector<gfx::grass_chunk> const &Chunks ) {}
|
||||
// shader methods
|
||||
virtual auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gl::program> = 0;
|
||||
virtual auto Fetch_Shader( std::string const &name ) -> std::shared_ptr<gfx::shader_program> = 0;
|
||||
// the name the program was fetched by, empty where it wasn't fetched by Fetch_Shader()
|
||||
virtual auto Shader_Name( std::shared_ptr<gl::program> const &Program ) const -> std::string { return {}; }
|
||||
virtual auto Shader_Name( std::shared_ptr<gfx::shader_program> const &Program ) const -> std::string { return {}; }
|
||||
// texture methods
|
||||
virtual auto Fetch_Texture( std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA ) -> texture_handle = 0;
|
||||
virtual void Bind_Texture( texture_handle const Texture ) = 0;
|
||||
@@ -228,16 +228,34 @@ private:
|
||||
class imgui_renderer
|
||||
{
|
||||
public:
|
||||
// how an image of the user interface is sampled
|
||||
struct image_options
|
||||
{
|
||||
bool srgb{true}; // the colours are srgb, made linear by the sampling; plain values otherwise
|
||||
bool nearest{false}; // the nearest texel instead of the linear filtering
|
||||
bool mipmaps{false}; // mipmaps made of the image, for an image drawn smaller than it is
|
||||
};
|
||||
|
||||
virtual bool Init() = 0;
|
||||
virtual void Shutdown() = 0;
|
||||
virtual void BeginFrame() = 0;
|
||||
virtual void Render() = 0;
|
||||
// images of the user interface itself (e.g. the previews of the node bank), apart from the textures of the scene: rgba, rows
|
||||
// from the top, in srgb. returns: id for ImGui::Image, 0 if the renderer can't make them
|
||||
virtual std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height)
|
||||
// from the top. returns: id for ImGui::Image, 0 if the renderer can't make them
|
||||
virtual std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height)
|
||||
{
|
||||
return Create_Image(Rgba, Width, Height, image_options{});
|
||||
}
|
||||
// the image given other content, of the same or another size (Image 0: a new one). returns: its id, which can be a new one
|
||||
virtual std::uint64_t Update_Image(std::uint64_t const Image, std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
|
||||
{
|
||||
Release_Image(Image);
|
||||
return Create_Image(Rgba, Width, Height, Options);
|
||||
}
|
||||
virtual void Release_Image(std::uint64_t const Image) {}
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
54
rendering/vulkan14/vulkan14bindings.h
Normal file
54
rendering/vulkan14/vulkan14bindings.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// the bindings of the shaders of the vulkan renderer, in one place for the c++ code and the shaders: the renderer puts them
|
||||
// before the sources of shaders/vk14 as #define lines (vk14::binding_preamble()), and shaders/vk14/common.glsl writes the
|
||||
// same values for the SPIR-V binaries, which glslc compiles without the renderer (the renderer checks they are the same).
|
||||
//
|
||||
// the descriptor sets (decision K6 of the plan): set 0 holds the resources of a pass, made when the renderer starts and bound
|
||||
// with a dynamic offset in the ring of the frame; set 1 is pushed with the draws (vkCmdPushDescriptorSet2): the textures of
|
||||
// the material and what changes between the draws. a layout can have only one set pushed, so the passes without a set of
|
||||
// their own (the user interface, the compute passes) push set 0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace vk14::bindings
|
||||
{
|
||||
|
||||
// the user interface (imgui): one pushed set with the texture, the transform in the push constants
|
||||
constexpr std::uint32_t UI_SET = 0;
|
||||
constexpr std::uint32_t UI_TEXTURE = 0;
|
||||
|
||||
// the sets of the passes of the scene (from the stage D1 of the plan)
|
||||
constexpr std::uint32_t PASS_SET = 0;
|
||||
constexpr std::uint32_t MATERIAL_SET = 1;
|
||||
// the textures of a material take the bindings of their units (#pragma texture(name, unit, ...)), 0 .. MAX_TEXTURES - 1
|
||||
constexpr std::uint32_t MATERIAL_TEXTURE_FIRST = 0;
|
||||
|
||||
// the specialization constants of the shaders
|
||||
// 1: the colour written to the window is encoded by the shader (a window without an srgb format, gfx.shadergamma yes)
|
||||
constexpr std::uint32_t SPEC_OUTPUT_GAMMA = 0;
|
||||
|
||||
} // namespace vk14::bindings
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
// the bindings above as #define lines, with MASZYNA_CONSTANTS, which tells shaders/vk14/common.glsl not to write its own
|
||||
inline std::string binding_preamble()
|
||||
{
|
||||
auto const define = [](char const *Name, std::uint32_t const Value) { return std::string("#define ") + Name + " " + std::to_string(Value) + "\n"; };
|
||||
return define("MASZYNA_CONSTANTS", 1) + define("UI_SET", bindings::UI_SET) + define("UI_TEXTURE", bindings::UI_TEXTURE) + define("PASS_SET", bindings::PASS_SET) + define("MATERIAL_SET", bindings::MATERIAL_SET) +
|
||||
define("MATERIAL_TEXTURE_FIRST", bindings::MATERIAL_TEXTURE_FIRST) + define("SPEC_OUTPUT_GAMMA", bindings::SPEC_OUTPUT_GAMMA);
|
||||
}
|
||||
|
||||
} // namespace vk14
|
||||
@@ -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());
|
||||
|
||||
@@ -17,6 +17,8 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "rendering/vulkan14/vulkan14common.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
@@ -72,6 +74,11 @@ struct capabilities
|
||||
std::uint32_t device_id{0};
|
||||
VkDeviceSize device_local_memory{0};
|
||||
float timestamp_period{0.f}; // nanoseconds per tick
|
||||
// the limits of the device, the identity of its pipeline cache (VkPipelineCache data of another device or driver is
|
||||
// thrown away) and the bindings a push descriptor set can have
|
||||
VkPhysicalDeviceLimits limits{};
|
||||
std::array<std::uint8_t, VK_UUID_SIZE> pipeline_cache_uuid{};
|
||||
std::uint32_t max_push_descriptors{0};
|
||||
|
||||
// device extensions
|
||||
bool ext_swapchain{false};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -31,6 +31,26 @@ std::string memory_size_string(VkDeviceSize Bytes);
|
||||
// logs a failed call and gives back false; true for VK_SUCCESS
|
||||
bool verify_result(VkResult Result, char const *Call, char const *File, int Line);
|
||||
|
||||
// whether VK_EXT_debug_utils is on (set by vk14::instance): the names of the objects and the labels of the passes
|
||||
extern bool debug_utils_enabled;
|
||||
// the 64 bit value of a handle, for the names of the objects
|
||||
template <typename Handle_> std::uint64_t handle_value(Handle_ Handle)
|
||||
{
|
||||
if constexpr (std::is_pointer_v<Handle_>)
|
||||
{
|
||||
return reinterpret_cast<std::uint64_t>(Handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
return static_cast<std::uint64_t>(Handle);
|
||||
}
|
||||
}
|
||||
// a name of an object for the messages of the validation layer and for captures; nothing without debug utils
|
||||
void name_object(VkDevice Device, VkObjectType Type, std::uint64_t Handle, std::string const &Name);
|
||||
// a label of a part of the commands (a pass), for captures and the messages of the validation layer
|
||||
void begin_label(VkCommandBuffer Commands, char const *Name);
|
||||
void end_label(VkCommandBuffer Commands);
|
||||
|
||||
// the log of the renderer, with the "vulkan: " prefix
|
||||
void log_info(std::string const &Message);
|
||||
void log_warning(std::string const &Message);
|
||||
|
||||
168
rendering/vulkan14/vulkan14descriptors.cpp
Normal file
168
rendering/vulkan14/vulkan14descriptors.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14descriptors.h"
|
||||
#include "rendering/vulkan14/vulkan14device.h"
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
// samplers
|
||||
|
||||
bool samplers::create(device const &Device, float Anisotropy, std::string &Reason)
|
||||
{
|
||||
auto const handle{Device.handle()};
|
||||
auto const maxanisotropy{Device.caps().limits.maxSamplerAnisotropy};
|
||||
auto const make = [&](VkSampler &Sampler, VkFilter Filter, VkSamplerAddressMode Address, bool Mipmaps, float Aniso, bool Compare, char const *Name) {
|
||||
VkSamplerCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
||||
info.magFilter = Filter;
|
||||
info.minFilter = Filter;
|
||||
info.mipmapMode = (Filter == VK_FILTER_LINEAR ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST);
|
||||
info.addressModeU = Address;
|
||||
info.addressModeV = Address;
|
||||
info.addressModeW = Address;
|
||||
info.maxLod = (Mipmaps ? VK_LOD_CLAMP_NONE : 0.f);
|
||||
if (Aniso > 1.f)
|
||||
{
|
||||
info.anisotropyEnable = VK_TRUE;
|
||||
info.maxAnisotropy = std::min(Aniso, maxanisotropy);
|
||||
}
|
||||
if (Compare)
|
||||
{
|
||||
// reverse z: the fragment is lit where it's nearer the light than the map, its depth greater
|
||||
info.compareEnable = VK_TRUE;
|
||||
info.compareOp = VK_COMPARE_OP_GREATER_OR_EQUAL;
|
||||
info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
|
||||
}
|
||||
if (!VK14_VERIFY(vkCreateSampler(handle, &info, nullptr, &Sampler)))
|
||||
{
|
||||
Sampler = VK_NULL_HANDLE;
|
||||
Reason = std::string("the sampler ") + Name + " can't be made";
|
||||
return false;
|
||||
}
|
||||
name_object(handle, VK_OBJECT_TYPE_SAMPLER, handle_value(Sampler), Name);
|
||||
return true;
|
||||
};
|
||||
return make(repeat_aniso, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_REPEAT, true, Anisotropy, false, "repeat aniso") && make(clamp_aniso, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, true, Anisotropy, false, "clamp aniso") &&
|
||||
make(clamp_linear, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, true, 1.f, false, "clamp linear") && make(clamp_nearest, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, false, 1.f, false, "clamp nearest") &&
|
||||
make(shadow, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, false, 1.f, true, "shadow");
|
||||
}
|
||||
|
||||
void samplers::destroy(VkDevice Device)
|
||||
{
|
||||
for (auto *sampler : {&repeat_aniso, &clamp_aniso, &clamp_linear, &clamp_nearest, &shadow})
|
||||
{
|
||||
if (*sampler != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroySampler(Device, *sampler, nullptr);
|
||||
*sampler = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pushed sets
|
||||
|
||||
bool push_set::create(VkDevice Device, std::vector<VkDescriptorSetLayoutBinding> const &Bindings, std::uint32_t Set, VkPipelineBindPoint BindPoint, std::size_t Stride, std::string const &Name)
|
||||
{
|
||||
m_bindings = Bindings;
|
||||
m_stride = Stride;
|
||||
set = Set;
|
||||
bind_point = BindPoint;
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
||||
info.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT;
|
||||
info.bindingCount = static_cast<std::uint32_t>(Bindings.size());
|
||||
info.pBindings = Bindings.data();
|
||||
if (!VK14_VERIFY(vkCreateDescriptorSetLayout(Device, &info, nullptr, &layout)))
|
||||
{
|
||||
layout = VK_NULL_HANDLE;
|
||||
return false;
|
||||
}
|
||||
name_object(Device, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, handle_value(layout), Name);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool push_set::create_template(VkDevice Device, VkPipelineLayout Layout, std::string const &Name)
|
||||
{
|
||||
std::vector<VkDescriptorUpdateTemplateEntry> entries;
|
||||
for (std::size_t i = 0; i < m_bindings.size(); ++i)
|
||||
{
|
||||
VkDescriptorUpdateTemplateEntry entry{};
|
||||
entry.dstBinding = m_bindings[i].binding;
|
||||
entry.dstArrayElement = 0;
|
||||
entry.descriptorCount = m_bindings[i].descriptorCount;
|
||||
entry.descriptorType = m_bindings[i].descriptorType;
|
||||
entry.offset = i * m_stride;
|
||||
entry.stride = m_stride;
|
||||
entries.push_back(entry);
|
||||
}
|
||||
VkDescriptorUpdateTemplateCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_CREATE_INFO;
|
||||
info.descriptorUpdateEntryCount = static_cast<std::uint32_t>(entries.size());
|
||||
info.pDescriptorUpdateEntries = entries.data();
|
||||
info.templateType = VK_DESCRIPTOR_UPDATE_TEMPLATE_TYPE_PUSH_DESCRIPTORS;
|
||||
info.descriptorSetLayout = layout;
|
||||
info.pipelineBindPoint = bind_point;
|
||||
info.pipelineLayout = Layout;
|
||||
info.set = set;
|
||||
if (!VK14_VERIFY(vkCreateDescriptorUpdateTemplate(Device, &info, nullptr, &update)))
|
||||
{
|
||||
update = VK_NULL_HANDLE;
|
||||
return false;
|
||||
}
|
||||
name_object(Device, VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE, handle_value(update), Name);
|
||||
return true;
|
||||
}
|
||||
|
||||
void push_set::destroy(VkDevice Device)
|
||||
{
|
||||
if (update != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyDescriptorUpdateTemplate(Device, update, nullptr);
|
||||
update = VK_NULL_HANDLE;
|
||||
}
|
||||
if (layout != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyDescriptorSetLayout(Device, layout, nullptr);
|
||||
layout = VK_NULL_HANDLE;
|
||||
}
|
||||
}
|
||||
|
||||
void push_set::push(VkCommandBuffer Commands, VkPipelineLayout Layout, void const *Data) const
|
||||
{
|
||||
VkPushDescriptorSetWithTemplateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_PUSH_DESCRIPTOR_SET_WITH_TEMPLATE_INFO;
|
||||
info.descriptorUpdateTemplate = update;
|
||||
info.layout = Layout;
|
||||
info.set = set;
|
||||
info.pData = Data;
|
||||
vkCmdPushDescriptorSetWithTemplate2(Commands, &info);
|
||||
}
|
||||
|
||||
VkPipelineLayout make_pipeline_layout(VkDevice Device, std::vector<VkDescriptorSetLayout> const &Sets, std::vector<VkPushConstantRange> const &PushConstants, std::string const &Name)
|
||||
{
|
||||
VkPipelineLayoutCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||
info.setLayoutCount = static_cast<std::uint32_t>(Sets.size());
|
||||
info.pSetLayouts = Sets.data();
|
||||
info.pushConstantRangeCount = static_cast<std::uint32_t>(PushConstants.size());
|
||||
info.pPushConstantRanges = PushConstants.data();
|
||||
VkPipelineLayout layout{VK_NULL_HANDLE};
|
||||
if (!VK14_VERIFY(vkCreatePipelineLayout(Device, &info, nullptr, &layout)))
|
||||
{
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
name_object(Device, VK_OBJECT_TYPE_PIPELINE_LAYOUT, handle_value(layout), Name);
|
||||
return layout;
|
||||
}
|
||||
|
||||
} // namespace vk14
|
||||
61
rendering/vulkan14/vulkan14descriptors.h
Normal file
61
rendering/vulkan14/vulkan14descriptors.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// the descriptors of the vulkan renderer without pools: the samplers every pass shares, the layouts of the pushed sets, and the
|
||||
// templates the descriptors are pushed by with the draws (vkCmdPushDescriptorSetWithTemplate2). no descriptor is allocated or
|
||||
// updated while a frame is made
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rendering/vulkan14/vulkan14common.h"
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
class device;
|
||||
|
||||
// the samplers of the renderer, made once
|
||||
struct samplers
|
||||
{
|
||||
VkSampler repeat_aniso{VK_NULL_HANDLE}; // the textures of the scene: repeated, mipmapped, anisotropic (anisotropicfiltering)
|
||||
VkSampler clamp_aniso{VK_NULL_HANDLE}; // the same clamped (textures with the s and t traits)
|
||||
VkSampler clamp_linear{VK_NULL_HANDLE}; // images of the user interface, post-processing
|
||||
VkSampler clamp_nearest{VK_NULL_HANDLE};
|
||||
VkSampler shadow{VK_NULL_HANDLE}; // depth compared (shadow maps, reverse z)
|
||||
|
||||
bool create(device const &Device, float Anisotropy, std::string &Reason);
|
||||
void destroy(VkDevice Device);
|
||||
};
|
||||
|
||||
// a layout of a pushed set and its template
|
||||
struct push_set
|
||||
{
|
||||
VkDescriptorSetLayout layout{VK_NULL_HANDLE};
|
||||
VkDescriptorUpdateTemplate update{VK_NULL_HANDLE};
|
||||
std::uint32_t set{0};
|
||||
VkPipelineBindPoint bind_point{VK_PIPELINE_BIND_POINT_GRAPHICS};
|
||||
|
||||
// Bindings: one descriptor each, the data of the template is an array of VkDescriptorImageInfo / VkDescriptorBufferInfo in
|
||||
// the order of the bindings (Stride bytes per binding)
|
||||
bool create(VkDevice Device, std::vector<VkDescriptorSetLayoutBinding> const &Bindings, std::uint32_t Set, VkPipelineBindPoint BindPoint, std::size_t Stride, std::string const &Name);
|
||||
// the template needs the pipeline layout the set is in
|
||||
bool create_template(VkDevice Device, VkPipelineLayout Layout, std::string const &Name);
|
||||
void destroy(VkDevice Device);
|
||||
// pushes Data (laid out as create() was told)
|
||||
void push(VkCommandBuffer Commands, VkPipelineLayout Layout, void const *Data) const;
|
||||
|
||||
private:
|
||||
std::vector<VkDescriptorSetLayoutBinding> m_bindings;
|
||||
std::size_t m_stride{0};
|
||||
};
|
||||
|
||||
// a pipeline layout of the given sets and push constants
|
||||
VkPipelineLayout make_pipeline_layout(VkDevice Device, std::vector<VkDescriptorSetLayout> const &Sets, std::vector<VkPushConstantRange> const &PushConstants, std::string const &Name);
|
||||
|
||||
} // namespace vk14
|
||||
@@ -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{};
|
||||
|
||||
@@ -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};
|
||||
|
||||
341
rendering/vulkan14/vulkan14imgui.cpp
Normal file
341
rendering/vulkan14/vulkan14imgui.cpp
Normal file
@@ -0,0 +1,341 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14imgui.h"
|
||||
#include "rendering/vulkan14/vulkan14bindings.h"
|
||||
#include "rendering/vulkan14/vulkan14device.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
bool imgui_backend::create(resources const &Resources, shader_library &Shaders, pipeline_library &Pipelines, frame_ring &Ring, std::string &Reason)
|
||||
{
|
||||
m_resources = &Resources;
|
||||
m_shaders = &Shaders;
|
||||
m_pipelines = &Pipelines;
|
||||
m_ring = &Ring;
|
||||
auto const device{Resources.gpu->handle()};
|
||||
|
||||
VkDescriptorSetLayoutBinding texture{};
|
||||
texture.binding = bindings::UI_TEXTURE;
|
||||
texture.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
texture.descriptorCount = 1;
|
||||
texture.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
if (false == m_set.create(device, {texture}, bindings::UI_SET, VK_PIPELINE_BIND_POINT_GRAPHICS, sizeof(VkDescriptorImageInfo), "ui texture"))
|
||||
{
|
||||
Reason = "no descriptor set layout for the user interface";
|
||||
return false;
|
||||
}
|
||||
m_layout = make_pipeline_layout(device, {m_set.layout}, {VkPushConstantRange{VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(float) * 4}}, "ui");
|
||||
if ((m_layout == VK_NULL_HANDLE) || (false == m_set.create_template(device, m_layout, "ui texture")))
|
||||
{
|
||||
Reason = "no pipeline layout for the user interface";
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
m_vertex = Shaders.get("imgui.vert");
|
||||
m_fragment = Shaders.get("imgui.frag");
|
||||
}
|
||||
catch (gfx::shader_exception const &e)
|
||||
{
|
||||
Reason = std::string("the shaders of the user interface: ") + e.what();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void imgui_backend::destroy()
|
||||
{
|
||||
if (m_resources == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const device{m_resources->gpu->handle()};
|
||||
for (auto const image : m_images)
|
||||
{
|
||||
m_resources->images->remove(image, *m_resources->deletions, m_resources->uploads->current_value());
|
||||
}
|
||||
m_images.clear();
|
||||
m_font = 0;
|
||||
if (ImGui::GetCurrentContext() != nullptr)
|
||||
{
|
||||
// imgui outlives the renderer (ui_layer::shutdown() comes after it)
|
||||
auto &io{ImGui::GetIO()};
|
||||
io.Fonts->SetTexID(0);
|
||||
io.BackendRendererName = nullptr;
|
||||
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
|
||||
}
|
||||
m_set.destroy(device);
|
||||
if (m_layout != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyPipelineLayout(device, m_layout, nullptr);
|
||||
m_layout = VK_NULL_HANDLE;
|
||||
}
|
||||
m_vertex.reset();
|
||||
m_fragment.reset();
|
||||
m_drawdata = nullptr;
|
||||
m_resources = nullptr;
|
||||
}
|
||||
|
||||
bool imgui_backend::Init()
|
||||
{
|
||||
auto &io{ImGui::GetIO()};
|
||||
io.BackendRendererName = "maszyna vulkan14";
|
||||
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
|
||||
BeginFrame();
|
||||
return (m_resources != nullptr);
|
||||
}
|
||||
|
||||
void imgui_backend::Shutdown()
|
||||
{
|
||||
auto &io{ImGui::GetIO()};
|
||||
if ((m_font != 0) && (m_resources != nullptr))
|
||||
{
|
||||
Release_Image(m_font);
|
||||
m_font = 0;
|
||||
}
|
||||
io.Fonts->SetTexID(0);
|
||||
io.BackendRendererName = nullptr;
|
||||
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
|
||||
m_drawdata = nullptr;
|
||||
}
|
||||
|
||||
void imgui_backend::BeginFrame()
|
||||
{
|
||||
if (m_resources == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// the font, made again when imgui built it anew
|
||||
auto &io{ImGui::GetIO()};
|
||||
if ((m_font != 0) && io.Fonts->IsBuilt() && (io.Fonts->TexID == m_font))
|
||||
{
|
||||
return;
|
||||
}
|
||||
unsigned char *pixels{nullptr};
|
||||
int width{0}, height{0};
|
||||
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
|
||||
image_options options;
|
||||
// the alpha of the glyphs as it is, as the opengl renderer has it
|
||||
options.srgb = false;
|
||||
m_font = Update_Image(m_font, pixels, width, height, options);
|
||||
io.Fonts->SetTexID(static_cast<ImTextureID>(m_font));
|
||||
}
|
||||
|
||||
void imgui_backend::Render()
|
||||
{
|
||||
m_drawdata = ImGui::GetDrawData();
|
||||
}
|
||||
|
||||
std::uint64_t imgui_backend::Create_Image(std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
|
||||
{
|
||||
return Update_Image(0, Rgba, Width, Height, Options);
|
||||
}
|
||||
|
||||
std::uint64_t imgui_backend::Update_Image(std::uint64_t const Id, std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options)
|
||||
{
|
||||
if ((m_resources == nullptr) || (Rgba == nullptr) || (Width <= 0) || (Height <= 0))
|
||||
{
|
||||
return Id;
|
||||
}
|
||||
// only the images made here are given new content; another id (released, or of a texture) gets a new image
|
||||
auto const *current{(std::find(m_images.begin(), m_images.end(), Id) != m_images.end()) ? m_resources->images->find(Id) : nullptr};
|
||||
auto const Image{current != nullptr ? Id : 0};
|
||||
auto const format{Options.srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM};
|
||||
auto const width{static_cast<std::uint32_t>(Width)}, height{static_cast<std::uint32_t>(Height)};
|
||||
auto const mipmaps{Options.mipmaps && m_resources->uploads->can_blit(format)};
|
||||
auto const sampler{Options.nearest ? m_resources->sampling->clamp_nearest : m_resources->sampling->clamp_linear};
|
||||
|
||||
std::shared_ptr<image> target;
|
||||
if (current != nullptr)
|
||||
{
|
||||
auto const &desc{current->target->desc()};
|
||||
if ((desc.extent.width == width) && (desc.extent.height == height) && (desc.format == format) && ((desc.mips > 1) == mipmaps))
|
||||
{
|
||||
// the same image given new content; the frames which drew it come first in the queue
|
||||
target = current->target;
|
||||
}
|
||||
}
|
||||
if (target == nullptr)
|
||||
{
|
||||
image_desc desc;
|
||||
desc.format = format;
|
||||
desc.extent = {width, height, 1};
|
||||
desc.mips = (mipmaps ? static_cast<std::uint32_t>(std::floor(std::log2(std::max(width, height)))) + 1 : 1);
|
||||
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | (mipmaps ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0);
|
||||
target = std::make_shared<image>();
|
||||
if (false == target->create(*m_resources->memory, desc, "ui image"))
|
||||
{
|
||||
return Id;
|
||||
}
|
||||
}
|
||||
if (false == m_resources->uploads->upload(target, Rgba, static_cast<VkDeviceSize>(width) * height * 4, {{0, {width, height, 1}}}, mipmaps))
|
||||
{
|
||||
return Id;
|
||||
}
|
||||
|
||||
if (Image != 0)
|
||||
{
|
||||
if (current->target != target)
|
||||
{
|
||||
// the old image goes once the frames which drew it are done
|
||||
m_resources->deletions->push(m_resources->uploads->current_value(), [old = current->target]() {});
|
||||
}
|
||||
m_resources->images->replace(Image, target, sampler);
|
||||
return Image;
|
||||
}
|
||||
auto const id{m_resources->images->add(target, sampler)};
|
||||
m_images.push_back(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
void imgui_backend::Release_Image(std::uint64_t const Image)
|
||||
{
|
||||
if ((m_resources == nullptr) || (Image == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const lookup{std::find(m_images.begin(), m_images.end(), Image)};
|
||||
if (lookup == m_images.end())
|
||||
{
|
||||
// not one of the images of the user interface
|
||||
return;
|
||||
}
|
||||
m_images.erase(lookup);
|
||||
m_resources->images->remove(Image, *m_resources->deletions, m_resources->uploads->current_value());
|
||||
}
|
||||
|
||||
void imgui_backend::record(VkCommandBuffer Commands, VkExtent2D Extent, VkFormat Format, bool Srgb)
|
||||
{
|
||||
auto const *drawdata{m_drawdata};
|
||||
m_drawdata = nullptr;
|
||||
if ((drawdata == nullptr) || (drawdata->CmdListsCount == 0) || (drawdata->TotalVtxCount == 0) || (m_resources == nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const scale{drawdata->FramebufferScale};
|
||||
if ((drawdata->DisplaySize.x <= 0.f) || (drawdata->DisplaySize.y <= 0.f))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pipeline_desc desc;
|
||||
desc.stages = {m_vertex, m_fragment};
|
||||
desc.layout = m_layout;
|
||||
desc.vertices.bindings = {VkVertexInputBindingDescription{0, sizeof(ImDrawVert), VK_VERTEX_INPUT_RATE_VERTEX}};
|
||||
desc.vertices.attributes = {VkVertexInputAttributeDescription{0, 0, VK_FORMAT_R32G32_SFLOAT, static_cast<std::uint32_t>(offsetof(ImDrawVert, pos))},
|
||||
VkVertexInputAttributeDescription{1, 0, VK_FORMAT_R32G32_SFLOAT, static_cast<std::uint32_t>(offsetof(ImDrawVert, uv))},
|
||||
VkVertexInputAttributeDescription{2, 0, VK_FORMAT_R8G8B8A8_UNORM, static_cast<std::uint32_t>(offsetof(ImDrawVert, col))}};
|
||||
desc.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
desc.blending = blend_mode::alpha;
|
||||
desc.colour_formats = {Format};
|
||||
// the colour encoded by the shader where the image of the window doesn't do it
|
||||
desc.specialization = {Srgb ? 0u : 1u};
|
||||
desc.name = "ui";
|
||||
auto const pipeline{m_pipelines->get(desc)};
|
||||
if (pipeline == VK_NULL_HANDLE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// the vertices and the indices of the frame, through its ring
|
||||
auto const vertexbytes{static_cast<VkDeviceSize>(drawdata->TotalVtxCount) * sizeof(ImDrawVert)};
|
||||
auto const indexbytes{static_cast<VkDeviceSize>(drawdata->TotalIdxCount) * sizeof(ImDrawIdx)};
|
||||
auto const vertices{m_ring->allocate(vertexbytes, sizeof(ImDrawVert))};
|
||||
auto const indices{m_ring->allocate(indexbytes, 4)};
|
||||
if (!vertices || !indices)
|
||||
{
|
||||
log_warning("the ring of the frame is too small for the user interface");
|
||||
return;
|
||||
}
|
||||
auto *vertexdata{vertices.data};
|
||||
auto *indexdata{indices.data};
|
||||
for (int list = 0; list < drawdata->CmdListsCount; ++list)
|
||||
{
|
||||
auto const *commands{drawdata->CmdLists[list]};
|
||||
std::memcpy(vertexdata, commands->VtxBuffer.Data, commands->VtxBuffer.Size * sizeof(ImDrawVert));
|
||||
std::memcpy(indexdata, commands->IdxBuffer.Data, commands->IdxBuffer.Size * sizeof(ImDrawIdx));
|
||||
vertexdata += commands->VtxBuffer.Size * sizeof(ImDrawVert);
|
||||
indexdata += commands->IdxBuffer.Size * sizeof(ImDrawIdx);
|
||||
}
|
||||
|
||||
vkCmdBindPipeline(Commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
|
||||
set_draw_state(Commands, Extent);
|
||||
vkCmdBindVertexBuffers(Commands, 0, 1, &vertices.buffer, &vertices.offset);
|
||||
vkCmdBindIndexBuffer(Commands, indices.buffer, indices.offset, sizeof(ImDrawIdx) == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32);
|
||||
|
||||
// the pixels of the display (top left origin, y down) to the clip space of vulkan (y down too)
|
||||
float transform[4];
|
||||
transform[0] = 2.f / drawdata->DisplaySize.x;
|
||||
transform[1] = 2.f / drawdata->DisplaySize.y;
|
||||
transform[2] = -1.f - drawdata->DisplayPos.x * transform[0];
|
||||
transform[3] = -1.f - drawdata->DisplayPos.y * transform[1];
|
||||
VkPushConstantsInfo push{};
|
||||
push.sType = VK_STRUCTURE_TYPE_PUSH_CONSTANTS_INFO;
|
||||
push.layout = m_layout;
|
||||
push.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
push.offset = 0;
|
||||
push.size = sizeof(transform);
|
||||
push.pValues = transform;
|
||||
vkCmdPushConstants2(Commands, &push);
|
||||
|
||||
auto const clipoffset{drawdata->DisplayPos};
|
||||
std::uint32_t vertexoffset{0}, indexoffset{0};
|
||||
std::uint64_t pushed{~0ull};
|
||||
for (int list = 0; list < drawdata->CmdListsCount; ++list)
|
||||
{
|
||||
auto const *commands{drawdata->CmdLists[list]};
|
||||
for (int index = 0; index < commands->CmdBuffer.Size; ++index)
|
||||
{
|
||||
auto const &command{commands->CmdBuffer[index]};
|
||||
if (command.UserCallback != nullptr)
|
||||
{
|
||||
// no callbacks in the user interface of the simulator; the one which resets the state is all there is to do
|
||||
if (command.UserCallback != ImDrawCallback_ResetRenderState)
|
||||
{
|
||||
command.UserCallback(commands, &command);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// the clip rectangle in the pixels of the image
|
||||
auto const minx{std::max((command.ClipRect.x - clipoffset.x) * scale.x, 0.f)};
|
||||
auto const miny{std::max((command.ClipRect.y - clipoffset.y) * scale.y, 0.f)};
|
||||
auto const maxx{std::min((command.ClipRect.z - clipoffset.x) * scale.x, static_cast<float>(Extent.width))};
|
||||
auto const maxy{std::min((command.ClipRect.w - clipoffset.y) * scale.y, static_cast<float>(Extent.height))};
|
||||
if ((maxx <= minx) || (maxy <= miny))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto const *entry{m_resources->images->find(command.GetTexID())};
|
||||
if (entry == nullptr)
|
||||
{
|
||||
// no image of that id (not made, or gone)
|
||||
continue;
|
||||
}
|
||||
VkRect2D scissor{{static_cast<std::int32_t>(minx), static_cast<std::int32_t>(miny)}, {static_cast<std::uint32_t>(maxx - minx), static_cast<std::uint32_t>(maxy - miny)}};
|
||||
vkCmdSetScissor(Commands, 0, 1, &scissor);
|
||||
if (pushed != command.GetTexID())
|
||||
{
|
||||
VkDescriptorImageInfo info{entry->sampler, entry->target->view(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL};
|
||||
m_set.push(Commands, m_layout, &info);
|
||||
pushed = command.GetTexID();
|
||||
}
|
||||
vkCmdDrawIndexed(Commands, command.ElemCount, 1, command.IdxOffset + indexoffset, static_cast<std::int32_t>(command.VtxOffset + vertexoffset), 0);
|
||||
}
|
||||
vertexoffset += static_cast<std::uint32_t>(commands->VtxBuffer.Size);
|
||||
indexoffset += static_cast<std::uint32_t>(commands->IdxBuffer.Size);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace vk14
|
||||
75
rendering/vulkan14/vulkan14imgui.h
Normal file
75
rendering/vulkan14/vulkan14imgui.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// the user interface (imgui) drawn by the vulkan renderer (decision K10 of the plan): a backend of its own instead of
|
||||
// imgui_impl_vulkan, which would need a pool of descriptors and a set per texture. the texture of a draw is pushed with it
|
||||
// (push descriptors), the vertices and the indices go through the ring of the frame, and ImTextureID is the id of the image in
|
||||
// the table of the renderer, the same for the textures (ITexture::get_id()), the images of the user interface (Create_Image())
|
||||
// and the font. the colours are mixed as the user interface of the opengl renderer mixes them: in linear light, encoded by the
|
||||
// srgb image of the window or, with gfx.shadergamma, by the shader
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rendering/renderer.h"
|
||||
#include "rendering/vulkan14/vulkan14descriptors.h"
|
||||
#include "rendering/vulkan14/vulkan14shaders.h"
|
||||
#include "rendering/vulkan14/vulkan14texture.h"
|
||||
|
||||
struct ImDrawData;
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
class imgui_backend : public imgui_renderer
|
||||
{
|
||||
public:
|
||||
// what the backend draws with, from the renderer
|
||||
bool create(resources const &Resources, shader_library &Shaders, pipeline_library &Pipelines, frame_ring &Ring, std::string &Reason);
|
||||
void destroy();
|
||||
|
||||
// imgui_renderer
|
||||
bool Init() override;
|
||||
void Shutdown() override;
|
||||
void BeginFrame() override;
|
||||
void Render() override;
|
||||
using imgui_renderer::Create_Image;
|
||||
std::uint64_t Create_Image(std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options) override;
|
||||
std::uint64_t Update_Image(std::uint64_t const Image, std::uint8_t const *Rgba, int const Width, int const Height, image_options const &Options) override;
|
||||
void Release_Image(std::uint64_t const Image) override;
|
||||
|
||||
// whether imgui made a frame to draw since the last record()
|
||||
bool has_frame() const
|
||||
{
|
||||
return m_drawdata != nullptr;
|
||||
}
|
||||
// records the frame of imgui into Commands, in a rendering to an image of Extent and Format begun by the renderer. Srgb: the
|
||||
// image encodes the colour itself
|
||||
void record(VkCommandBuffer Commands, VkExtent2D Extent, VkFormat Format, bool Srgb);
|
||||
// the frame of imgui is dropped (nothing to draw it to)
|
||||
void skip()
|
||||
{
|
||||
m_drawdata = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
resources const *m_resources{nullptr};
|
||||
shader_library *m_shaders{nullptr};
|
||||
pipeline_library *m_pipelines{nullptr};
|
||||
frame_ring *m_ring{nullptr};
|
||||
push_set m_set;
|
||||
VkPipelineLayout m_layout{VK_NULL_HANDLE};
|
||||
std::shared_ptr<shader_code const> m_vertex;
|
||||
std::shared_ptr<shader_code const> m_fragment;
|
||||
std::uint64_t m_font{0};
|
||||
ImDrawData *m_drawdata{nullptr};
|
||||
// the images made by Create_Image()
|
||||
std::vector<std::uint64_t> m_images;
|
||||
};
|
||||
|
||||
} // namespace vk14
|
||||
@@ -249,6 +249,7 @@ bool instance::collect_extensions(std::vector<char const *> &Extensions, instanc
|
||||
{
|
||||
Extensions.clear();
|
||||
m_debug_utils_enabled = false;
|
||||
debug_utils_enabled = false;
|
||||
|
||||
std::uint32_t glfwcount{0};
|
||||
auto const **glfwextensions{glfwGetRequiredInstanceExtensions(&glfwcount)};
|
||||
@@ -281,6 +282,7 @@ bool instance::collect_extensions(std::vector<char const *> &Extensions, instanc
|
||||
Extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
}
|
||||
m_debug_utils_enabled = true;
|
||||
debug_utils_enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -337,6 +339,7 @@ void instance::destroy()
|
||||
}
|
||||
m_validation_enabled = false;
|
||||
m_debug_utils_enabled = false;
|
||||
debug_utils_enabled = false;
|
||||
}
|
||||
|
||||
} // namespace vk14
|
||||
|
||||
380
rendering/vulkan14/vulkan14memory.cpp
Normal file
380
rendering/vulkan14/vulkan14memory.cpp
Normal file
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14memory.h"
|
||||
#include "rendering/vulkan14/vulkan14instance.h"
|
||||
#include "rendering/vulkan14/vulkan14device.h"
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
// allocator
|
||||
|
||||
allocator::~allocator()
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
bool allocator::create(instance const &Instance, device const &Device, std::string &Reason)
|
||||
{
|
||||
VmaAllocatorCreateInfo info{};
|
||||
info.vulkanApiVersion = VK_API_VERSION_1_4;
|
||||
info.instance = Instance.handle();
|
||||
info.physicalDevice = Device.physical();
|
||||
info.device = Device.handle();
|
||||
info.flags = VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_BIT;
|
||||
if (Device.memory_budget_enabled())
|
||||
{
|
||||
info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
|
||||
}
|
||||
// the functions as volk loaded them
|
||||
VmaVulkanFunctions functions{};
|
||||
if (!VK14_VERIFY(vmaImportVulkanFunctionsFromVolk(&info, &functions)))
|
||||
{
|
||||
Reason = "the functions of the memory allocator can't be loaded";
|
||||
return false;
|
||||
}
|
||||
info.pVulkanFunctions = &functions;
|
||||
if (!VK14_VERIFY(vmaCreateAllocator(&info, &m_allocator)))
|
||||
{
|
||||
m_allocator = VK_NULL_HANDLE;
|
||||
Reason = "the memory allocator can't be made";
|
||||
return false;
|
||||
}
|
||||
m_device = Device.handle();
|
||||
|
||||
VkPhysicalDeviceMemoryProperties memory{};
|
||||
vkGetPhysicalDeviceMemoryProperties(Device.physical(), &memory);
|
||||
m_heapcount = memory.memoryHeapCount;
|
||||
m_deviceheaps.assign(m_heapcount, false);
|
||||
for (std::uint32_t i = 0; i < m_heapcount; ++i)
|
||||
{
|
||||
m_deviceheaps[i] = ((memory.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void allocator::destroy()
|
||||
{
|
||||
if (m_allocator != VK_NULL_HANDLE)
|
||||
{
|
||||
vmaDestroyAllocator(m_allocator);
|
||||
m_allocator = VK_NULL_HANDLE;
|
||||
}
|
||||
m_device = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void allocator::budget(VkDeviceSize &Usage, VkDeviceSize &Budget) const
|
||||
{
|
||||
Usage = 0;
|
||||
Budget = 0;
|
||||
if (m_allocator == VK_NULL_HANDLE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::vector<VmaBudget> budgets(m_heapcount);
|
||||
vmaGetHeapBudgets(m_allocator, budgets.data());
|
||||
for (std::uint32_t i = 0; i < m_heapcount; ++i)
|
||||
{
|
||||
if (m_deviceheaps[i])
|
||||
{
|
||||
Usage += budgets[i].usage;
|
||||
Budget += budgets[i].budget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// buffer
|
||||
|
||||
buffer::buffer(buffer &&Other) noexcept
|
||||
{
|
||||
*this = std::move(Other);
|
||||
}
|
||||
|
||||
buffer &buffer::operator=(buffer &&Other) noexcept
|
||||
{
|
||||
if (this != &Other)
|
||||
{
|
||||
destroy();
|
||||
std::swap(m_allocator, Other.m_allocator);
|
||||
std::swap(m_buffer, Other.m_buffer);
|
||||
std::swap(m_allocation, Other.m_allocation);
|
||||
std::swap(m_size, Other.m_size);
|
||||
std::swap(m_mapped, Other.m_mapped);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
buffer::~buffer()
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
bool buffer::create(allocator const &Allocator, VkDeviceSize Size, VkBufferUsageFlags Usage, memory_kind Kind, std::string const &Name)
|
||||
{
|
||||
destroy();
|
||||
|
||||
VkBufferCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
info.size = std::max<VkDeviceSize>(Size, 4);
|
||||
info.usage = Usage;
|
||||
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
|
||||
VmaAllocationCreateInfo allocation{};
|
||||
allocation.usage = VMA_MEMORY_USAGE_AUTO;
|
||||
switch (Kind)
|
||||
{
|
||||
case memory_kind::device:
|
||||
allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
||||
break;
|
||||
case memory_kind::upload:
|
||||
allocation.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
||||
break;
|
||||
case memory_kind::readback:
|
||||
allocation.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
||||
break;
|
||||
}
|
||||
|
||||
VmaAllocationInfo allocated{};
|
||||
if (!VK14_VERIFY(vmaCreateBuffer(Allocator.handle(), &info, &allocation, &m_buffer, &m_allocation, &allocated)))
|
||||
{
|
||||
m_buffer = VK_NULL_HANDLE;
|
||||
m_allocation = VK_NULL_HANDLE;
|
||||
return false;
|
||||
}
|
||||
m_allocator = Allocator.handle();
|
||||
m_size = Size;
|
||||
m_mapped = static_cast<std::uint8_t *>(allocated.pMappedData);
|
||||
name_object(Allocator.device_handle(), VK_OBJECT_TYPE_BUFFER, handle_value(m_buffer), Name);
|
||||
return true;
|
||||
}
|
||||
|
||||
void buffer::destroy()
|
||||
{
|
||||
if (m_buffer != VK_NULL_HANDLE)
|
||||
{
|
||||
vmaDestroyBuffer(m_allocator, m_buffer, m_allocation);
|
||||
}
|
||||
m_buffer = VK_NULL_HANDLE;
|
||||
m_allocation = VK_NULL_HANDLE;
|
||||
m_size = 0;
|
||||
m_mapped = nullptr;
|
||||
}
|
||||
|
||||
void buffer::flush(VkDeviceSize Offset, VkDeviceSize Size) const
|
||||
{
|
||||
if ((m_allocation != VK_NULL_HANDLE) && (Size > 0))
|
||||
{
|
||||
// vma does nothing for coherent memory
|
||||
vmaFlushAllocation(m_allocator, m_allocation, Offset, Size);
|
||||
}
|
||||
}
|
||||
|
||||
// image
|
||||
|
||||
image::~image()
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
VkImageAspectFlags image::aspect() const
|
||||
{
|
||||
switch (m_desc.format)
|
||||
{
|
||||
case VK_FORMAT_D16_UNORM:
|
||||
case VK_FORMAT_D32_SFLOAT:
|
||||
case VK_FORMAT_X8_D24_UNORM_PACK32:
|
||||
return VK_IMAGE_ASPECT_DEPTH_BIT;
|
||||
case VK_FORMAT_D24_UNORM_S8_UINT:
|
||||
case VK_FORMAT_D32_SFLOAT_S8_UINT:
|
||||
case VK_FORMAT_D16_UNORM_S8_UINT:
|
||||
return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
}
|
||||
|
||||
bool image::create(allocator const &Allocator, image_desc const &Desc, std::string const &Name)
|
||||
{
|
||||
destroy();
|
||||
m_desc = Desc;
|
||||
|
||||
VkImageCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
info.flags = Desc.flags;
|
||||
info.imageType = Desc.type;
|
||||
info.format = Desc.format;
|
||||
info.extent = Desc.extent;
|
||||
info.mipLevels = std::max(Desc.mips, 1u);
|
||||
info.arrayLayers = std::max(Desc.layers, 1u);
|
||||
info.samples = Desc.samples;
|
||||
info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
info.usage = Desc.usage;
|
||||
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
|
||||
VmaAllocationCreateInfo allocation{};
|
||||
allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
||||
VmaAllocationInfo allocated{};
|
||||
if (!VK14_VERIFY(vmaCreateImage(Allocator.handle(), &info, &allocation, &m_image, &m_allocation, &allocated)))
|
||||
{
|
||||
m_image = VK_NULL_HANDLE;
|
||||
m_allocation = VK_NULL_HANDLE;
|
||||
return false;
|
||||
}
|
||||
m_allocator = Allocator.handle();
|
||||
m_device = Allocator.device_handle();
|
||||
m_bytes = allocated.size;
|
||||
|
||||
VkImageViewCreateInfo viewinfo{};
|
||||
viewinfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
viewinfo.image = m_image;
|
||||
viewinfo.viewType = Desc.view_type;
|
||||
viewinfo.format = Desc.format;
|
||||
viewinfo.components = Desc.components;
|
||||
viewinfo.subresourceRange = {aspect(), 0, info.mipLevels, 0, info.arrayLayers};
|
||||
if (!VK14_VERIFY(vkCreateImageView(m_device, &viewinfo, nullptr, &m_view)))
|
||||
{
|
||||
m_view = VK_NULL_HANDLE;
|
||||
destroy();
|
||||
return false;
|
||||
}
|
||||
state = image_state{};
|
||||
name_object(m_device, VK_OBJECT_TYPE_IMAGE, handle_value(m_image), Name);
|
||||
name_object(m_device, VK_OBJECT_TYPE_IMAGE_VIEW, handle_value(m_view), Name);
|
||||
return true;
|
||||
}
|
||||
|
||||
void image::destroy()
|
||||
{
|
||||
if (m_view != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyImageView(m_device, m_view, nullptr);
|
||||
}
|
||||
if (m_image != VK_NULL_HANDLE)
|
||||
{
|
||||
vmaDestroyImage(m_allocator, m_image, m_allocation);
|
||||
}
|
||||
m_view = VK_NULL_HANDLE;
|
||||
m_image = VK_NULL_HANDLE;
|
||||
m_allocation = VK_NULL_HANDLE;
|
||||
m_bytes = 0;
|
||||
state = image_state{};
|
||||
}
|
||||
|
||||
void transition(VkCommandBuffer Commands, image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access)
|
||||
{
|
||||
auto const writes{VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT |
|
||||
VK_ACCESS_2_HOST_WRITE_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT};
|
||||
auto const &from{Image.state};
|
||||
// reads after reads in the same layout need nothing
|
||||
if ((from.layout == Layout) && ((from.access & writes) == 0) && ((Access & writes) == 0))
|
||||
{
|
||||
Image.state.stage |= Stage;
|
||||
Image.state.access |= Access;
|
||||
return;
|
||||
}
|
||||
VkImageMemoryBarrier2 barrier{};
|
||||
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
|
||||
barrier.srcStageMask = (from.stage != VK_PIPELINE_STAGE_2_NONE ? from.stage : VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT);
|
||||
barrier.srcAccessMask = (from.access & writes);
|
||||
barrier.dstStageMask = Stage;
|
||||
barrier.dstAccessMask = Access;
|
||||
barrier.oldLayout = from.layout;
|
||||
barrier.newLayout = Layout;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = Image.handle();
|
||||
barrier.subresourceRange = {Image.aspect(), 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS};
|
||||
|
||||
VkDependencyInfo dependency{};
|
||||
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
dependency.imageMemoryBarrierCount = 1;
|
||||
dependency.pImageMemoryBarriers = &barrier;
|
||||
vkCmdPipelineBarrier2(Commands, &dependency);
|
||||
|
||||
Image.state = image_state{Layout, Stage, Access};
|
||||
}
|
||||
|
||||
// deletion queue
|
||||
|
||||
void deletion_queue::push(std::uint64_t Value, std::function<void()> &&Deleter)
|
||||
{
|
||||
m_entries.emplace_back(Value, std::move(Deleter));
|
||||
}
|
||||
|
||||
void deletion_queue::collect(std::uint64_t Completed)
|
||||
{
|
||||
// the values only grow, so the queue is in their order
|
||||
while ((false == m_entries.empty()) && (m_entries.front().first <= Completed))
|
||||
{
|
||||
auto deleter{std::move(m_entries.front().second)};
|
||||
m_entries.pop_front();
|
||||
deleter();
|
||||
}
|
||||
}
|
||||
|
||||
void deletion_queue::flush()
|
||||
{
|
||||
while (false == m_entries.empty())
|
||||
{
|
||||
auto deleter{std::move(m_entries.front().second)};
|
||||
m_entries.pop_front();
|
||||
deleter();
|
||||
}
|
||||
}
|
||||
|
||||
// frame ring
|
||||
|
||||
bool frame_ring::create(allocator const &Allocator, VkDeviceSize SizePerFrame, std::uint32_t Frames, VkBufferUsageFlags Usage, std::string const &Name)
|
||||
{
|
||||
m_sizeperframe = SizePerFrame;
|
||||
m_base = 0;
|
||||
m_used = 0;
|
||||
return m_buffer.create(Allocator, SizePerFrame * Frames, Usage, memory_kind::upload, Name) && (m_buffer.mapped() != nullptr);
|
||||
}
|
||||
|
||||
void frame_ring::destroy()
|
||||
{
|
||||
m_buffer.destroy();
|
||||
m_sizeperframe = 0;
|
||||
m_used = 0;
|
||||
}
|
||||
|
||||
void frame_ring::begin_frame(std::uint32_t Frame)
|
||||
{
|
||||
m_base = m_sizeperframe * Frame;
|
||||
m_used = 0;
|
||||
}
|
||||
|
||||
ring_slice frame_ring::allocate(VkDeviceSize Size, VkDeviceSize Alignment)
|
||||
{
|
||||
auto const alignment{std::max<VkDeviceSize>(Alignment, 4)};
|
||||
auto const offset{(m_used + alignment - 1) / alignment * alignment};
|
||||
if ((m_buffer.mapped() == nullptr) || (offset + Size > m_sizeperframe))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
m_used = offset + Size;
|
||||
ring_slice slice;
|
||||
slice.buffer = m_buffer.handle();
|
||||
slice.offset = m_base + offset;
|
||||
slice.size = Size;
|
||||
slice.data = m_buffer.mapped() + slice.offset;
|
||||
return slice;
|
||||
}
|
||||
|
||||
void frame_ring::flush() const
|
||||
{
|
||||
m_buffer.flush(m_base, m_used);
|
||||
}
|
||||
|
||||
} // namespace vk14
|
||||
241
rendering/vulkan14/vulkan14memory.h
Normal file
241
rendering/vulkan14/vulkan14memory.h
Normal file
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// the memory of the vulkan renderer: the allocator (VMA), buffers and images which give their memory back when they go, the
|
||||
// ring each frame in flight streams its data through, the uploads of data to the gpu through staging buffers, and the queue
|
||||
// of objects deleted once the gpu is done with the frames which used them (by the value of the timeline semaphore)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rendering/vulkan14/vulkan14common.h"
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
class instance;
|
||||
class device;
|
||||
|
||||
// the allocator of the device memory
|
||||
class allocator
|
||||
{
|
||||
public:
|
||||
allocator() = default;
|
||||
allocator(allocator const &) = delete;
|
||||
allocator &operator=(allocator const &) = delete;
|
||||
~allocator();
|
||||
|
||||
bool create(instance const &Instance, device const &Device, std::string &Reason);
|
||||
void destroy();
|
||||
|
||||
VmaAllocator handle() const
|
||||
{
|
||||
return m_allocator;
|
||||
}
|
||||
VkDevice device_handle() const
|
||||
{
|
||||
return m_device;
|
||||
}
|
||||
// device memory used and the budget of the heaps which are local to the device, bytes
|
||||
void budget(VkDeviceSize &Usage, VkDeviceSize &Budget) const;
|
||||
|
||||
private:
|
||||
VmaAllocator m_allocator{VK_NULL_HANDLE};
|
||||
VkDevice m_device{VK_NULL_HANDLE};
|
||||
std::uint32_t m_heapcount{0};
|
||||
std::vector<bool> m_deviceheaps;
|
||||
};
|
||||
|
||||
// where the memory of a buffer is
|
||||
enum class memory_kind
|
||||
{
|
||||
device, // device local, filled by copies
|
||||
upload, // seen by the cpu, written in order (staging, streamed data); device local where the device has such memory
|
||||
readback // seen by the cpu, cached, for the data read back from the gpu
|
||||
};
|
||||
|
||||
class buffer
|
||||
{
|
||||
public:
|
||||
buffer() = default;
|
||||
buffer(buffer const &) = delete;
|
||||
buffer &operator=(buffer const &) = delete;
|
||||
buffer(buffer &&Other) noexcept;
|
||||
buffer &operator=(buffer &&Other) noexcept;
|
||||
~buffer();
|
||||
|
||||
bool create(allocator const &Allocator, VkDeviceSize Size, VkBufferUsageFlags Usage, memory_kind Kind, std::string const &Name = {});
|
||||
void destroy();
|
||||
|
||||
VkBuffer handle() const
|
||||
{
|
||||
return m_buffer;
|
||||
}
|
||||
VkDeviceSize size() const
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
// the memory as the cpu sees it, null for memory the cpu doesn't see. the upload and readback memory stays mapped
|
||||
std::uint8_t *mapped() const
|
||||
{
|
||||
return m_mapped;
|
||||
}
|
||||
// makes the writes of the cpu seen by the gpu where the memory isn't coherent
|
||||
void flush(VkDeviceSize Offset, VkDeviceSize Size) const;
|
||||
explicit operator bool() const
|
||||
{
|
||||
return m_buffer != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
private:
|
||||
VmaAllocator m_allocator{VK_NULL_HANDLE};
|
||||
VkBuffer m_buffer{VK_NULL_HANDLE};
|
||||
VmaAllocation m_allocation{VK_NULL_HANDLE};
|
||||
VkDeviceSize m_size{0};
|
||||
std::uint8_t *m_mapped{nullptr};
|
||||
};
|
||||
|
||||
// the state of an image the barriers start from: its layout and the last stage and access which used it
|
||||
struct image_state
|
||||
{
|
||||
VkImageLayout layout{VK_IMAGE_LAYOUT_UNDEFINED};
|
||||
VkPipelineStageFlags2 stage{VK_PIPELINE_STAGE_2_NONE};
|
||||
VkAccessFlags2 access{VK_ACCESS_2_NONE};
|
||||
};
|
||||
|
||||
struct image_desc
|
||||
{
|
||||
VkImageType type{VK_IMAGE_TYPE_2D};
|
||||
VkImageViewType view_type{VK_IMAGE_VIEW_TYPE_2D};
|
||||
VkFormat format{VK_FORMAT_R8G8B8A8_UNORM};
|
||||
VkExtent3D extent{1, 1, 1};
|
||||
std::uint32_t mips{1};
|
||||
std::uint32_t layers{1};
|
||||
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
|
||||
VkImageUsageFlags usage{VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT};
|
||||
VkImageCreateFlags flags{0};
|
||||
// the channels the default view reads, e.g. a single channel texture seen as rrr1
|
||||
VkComponentMapping components{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY};
|
||||
};
|
||||
|
||||
class image
|
||||
{
|
||||
public:
|
||||
image() = default;
|
||||
image(image const &) = delete;
|
||||
image &operator=(image const &) = delete;
|
||||
~image();
|
||||
|
||||
// a device local image with a view of all its levels and layers
|
||||
bool create(allocator const &Allocator, image_desc const &Desc, std::string const &Name = {});
|
||||
void destroy();
|
||||
|
||||
VkImage handle() const
|
||||
{
|
||||
return m_image;
|
||||
}
|
||||
VkImageView view() const
|
||||
{
|
||||
return m_view;
|
||||
}
|
||||
image_desc const &desc() const
|
||||
{
|
||||
return m_desc;
|
||||
}
|
||||
VkImageAspectFlags aspect() const;
|
||||
VkDeviceSize bytes() const
|
||||
{
|
||||
return m_bytes;
|
||||
}
|
||||
// the state the next barrier starts from (the pass list and the uploads keep it)
|
||||
image_state state{};
|
||||
explicit operator bool() const
|
||||
{
|
||||
return m_image != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
private:
|
||||
VmaAllocator m_allocator{VK_NULL_HANDLE};
|
||||
VkDevice m_device{VK_NULL_HANDLE};
|
||||
VkImage m_image{VK_NULL_HANDLE};
|
||||
VkImageView m_view{VK_NULL_HANDLE};
|
||||
VmaAllocation m_allocation{VK_NULL_HANDLE};
|
||||
VkDeviceSize m_bytes{0};
|
||||
image_desc m_desc;
|
||||
};
|
||||
|
||||
// a barrier of an image from its state to the given one, the state of the image updated. nothing when the image already is
|
||||
// in the layout and was only read by the same stage
|
||||
void transition(VkCommandBuffer Commands, image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access);
|
||||
|
||||
// objects destroyed once the gpu is done with the frame which used them last
|
||||
class deletion_queue
|
||||
{
|
||||
public:
|
||||
// Value: the value of the timeline semaphore the gpu reaches when it's done with the object
|
||||
void push(std::uint64_t Value, std::function<void()> &&Deleter);
|
||||
// runs the deleters of the values the gpu reached
|
||||
void collect(std::uint64_t Completed);
|
||||
// runs all of them (the device is idle)
|
||||
void flush();
|
||||
std::size_t size() const
|
||||
{
|
||||
return m_entries.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::deque<std::pair<std::uint64_t, std::function<void()>>> m_entries;
|
||||
};
|
||||
|
||||
// a part of the ring of a frame: where the data goes on the cpu, and where the gpu reads it
|
||||
struct ring_slice
|
||||
{
|
||||
std::uint8_t *data{nullptr};
|
||||
VkBuffer buffer{VK_NULL_HANDLE};
|
||||
VkDeviceSize offset{0};
|
||||
VkDeviceSize size{0};
|
||||
explicit operator bool() const
|
||||
{
|
||||
return data != nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
// the data streamed by the frames: one mapped buffer split into a part for each frame in flight, the part of a frame taken
|
||||
// from the start again once the gpu is done with the frame which used it before (the counterpart of gl::frame_stream)
|
||||
class frame_ring
|
||||
{
|
||||
public:
|
||||
bool create(allocator const &Allocator, VkDeviceSize SizePerFrame, std::uint32_t Frames, VkBufferUsageFlags Usage, std::string const &Name);
|
||||
void destroy();
|
||||
// the part of the frame Frame is used from its start again
|
||||
void begin_frame(std::uint32_t Frame);
|
||||
// Size bytes aligned to Alignment; empty when the part of the frame is full
|
||||
ring_slice allocate(VkDeviceSize Size, VkDeviceSize Alignment);
|
||||
// makes the writes of the frame seen by the gpu, where the memory isn't coherent
|
||||
void flush() const;
|
||||
VkDeviceSize used() const
|
||||
{
|
||||
return m_used;
|
||||
}
|
||||
VkDeviceSize capacity() const
|
||||
{
|
||||
return m_sizeperframe;
|
||||
}
|
||||
|
||||
private:
|
||||
buffer m_buffer;
|
||||
VkDeviceSize m_sizeperframe{0};
|
||||
VkDeviceSize m_base{0};
|
||||
VkDeviceSize m_used{0};
|
||||
};
|
||||
|
||||
} // namespace vk14
|
||||
237
rendering/vulkan14/vulkan14passes.cpp
Normal file
237
rendering/vulkan14/vulkan14passes.cpp
Normal file
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14passes.h"
|
||||
#include "rendering/vulkan14/vulkan14device.h"
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
image_use use_of(image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access)
|
||||
{
|
||||
return image_use{Image.handle(), Image.aspect(), &Image.state, Layout, Stage, Access};
|
||||
}
|
||||
|
||||
void barriers(VkCommandBuffer Commands, std::vector<image_use> const &Uses)
|
||||
{
|
||||
auto const writes{VK_ACCESS_2_SHADER_WRITE_BIT | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT |
|
||||
VK_ACCESS_2_HOST_WRITE_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT};
|
||||
std::vector<VkImageMemoryBarrier2> list;
|
||||
for (auto const &use : Uses)
|
||||
{
|
||||
if ((use.image == VK_NULL_HANDLE) || (use.state == nullptr))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto &state{*use.state};
|
||||
// reads after reads in the same layout need nothing
|
||||
if ((state.layout == use.layout) && ((state.access & writes) == 0) && ((use.access & writes) == 0))
|
||||
{
|
||||
state.stage |= use.stage;
|
||||
state.access |= use.access;
|
||||
continue;
|
||||
}
|
||||
VkImageMemoryBarrier2 barrier{};
|
||||
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
|
||||
barrier.srcStageMask = (state.stage != VK_PIPELINE_STAGE_2_NONE ? state.stage : VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT);
|
||||
barrier.srcAccessMask = (state.access & writes);
|
||||
barrier.dstStageMask = use.stage;
|
||||
barrier.dstAccessMask = use.access;
|
||||
barrier.oldLayout = state.layout;
|
||||
barrier.newLayout = use.layout;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = use.image;
|
||||
barrier.subresourceRange = {use.aspect, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS};
|
||||
list.push_back(barrier);
|
||||
state = image_state{use.layout, use.stage, use.access};
|
||||
}
|
||||
if (list.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
VkDependencyInfo dependency{};
|
||||
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
dependency.imageMemoryBarrierCount = static_cast<std::uint32_t>(list.size());
|
||||
dependency.pImageMemoryBarriers = list.data();
|
||||
vkCmdPipelineBarrier2(Commands, &dependency);
|
||||
}
|
||||
|
||||
pass_list::~pass_list()
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
bool pass_list::create(device const &Device, std::uint32_t Frames, std::string &Reason)
|
||||
{
|
||||
m_device = Device.handle();
|
||||
m_period = Device.caps().timestamp_period;
|
||||
// the bits of the timestamps of the queue the frames go to
|
||||
std::uint32_t count{0};
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(Device.physical(), &count, nullptr);
|
||||
std::vector<VkQueueFamilyProperties> families(count);
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(Device.physical(), &count, families.data());
|
||||
auto const bits{(Device.families().graphics < count) ? families[Device.families().graphics].timestampValidBits : 64u};
|
||||
m_mask = (bits >= 64 ? ~0ull : ((1ull << bits) - 1));
|
||||
|
||||
m_frames.resize(Frames);
|
||||
for (std::uint32_t i = 0; i < Frames; ++i)
|
||||
{
|
||||
VkQueryPoolCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
|
||||
info.queryType = VK_QUERY_TYPE_TIMESTAMP;
|
||||
info.queryCount = max_passes * 2;
|
||||
if (!VK14_VERIFY(vkCreateQueryPool(m_device, &info, nullptr, &m_frames[i].pool)))
|
||||
{
|
||||
m_frames[i].pool = VK_NULL_HANDLE;
|
||||
Reason = "no query pool for the times of the passes";
|
||||
return false;
|
||||
}
|
||||
// the queries are reset from the host before their first use (hostQueryReset)
|
||||
vkResetQueryPool(m_device, m_frames[i].pool, 0, info.queryCount);
|
||||
name_object(m_device, VK_OBJECT_TYPE_QUERY_POOL, handle_value(m_frames[i].pool), "pass times " + std::to_string(i));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void pass_list::destroy()
|
||||
{
|
||||
for (auto &frame : m_frames)
|
||||
{
|
||||
if (frame.pool != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyQueryPool(m_device, frame.pool, nullptr);
|
||||
}
|
||||
}
|
||||
m_frames.clear();
|
||||
m_device = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void pass_list::begin_frame(VkCommandBuffer Commands, std::uint32_t Frame, std::uint64_t Number)
|
||||
{
|
||||
m_commands = Commands;
|
||||
m_frame = Frame;
|
||||
m_open = -1;
|
||||
auto &frame{m_frames[Frame]};
|
||||
frame.names.clear();
|
||||
frame.used = 0;
|
||||
frame.number = Number;
|
||||
frame.recorded = true;
|
||||
vkCmdResetQueryPool(Commands, frame.pool, 0, max_passes * 2);
|
||||
}
|
||||
|
||||
void pass_list::begin_pass(char const *Name, std::vector<image_use> const &Uses)
|
||||
{
|
||||
if (m_open >= 0)
|
||||
{
|
||||
end_pass();
|
||||
}
|
||||
barriers(m_commands, Uses);
|
||||
begin_label(m_commands, Name);
|
||||
auto &frame{m_frames[m_frame]};
|
||||
// (no timestamps on a queue without valid bits; the device is chosen with them, this is for safety)
|
||||
if ((frame.used < max_passes) && (m_mask != 0))
|
||||
{
|
||||
vkCmdWriteTimestamp2(m_commands, VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, frame.pool, frame.used * 2);
|
||||
frame.names.emplace_back(Name);
|
||||
m_open = static_cast<int>(frame.used);
|
||||
++frame.used;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_open = static_cast<int>(max_passes);
|
||||
}
|
||||
}
|
||||
|
||||
void pass_list::end_pass()
|
||||
{
|
||||
if (m_open < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (m_open < static_cast<int>(max_passes))
|
||||
{
|
||||
vkCmdWriteTimestamp2(m_commands, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, m_frames[m_frame].pool, static_cast<std::uint32_t>(m_open) * 2 + 1);
|
||||
}
|
||||
end_label(m_commands);
|
||||
m_open = -1;
|
||||
}
|
||||
|
||||
void pass_list::end_frame()
|
||||
{
|
||||
end_pass();
|
||||
m_commands = VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void pass_list::collect(std::uint32_t Frame)
|
||||
{
|
||||
auto &frame{m_frames[Frame]};
|
||||
if ((false == frame.recorded) || (frame.used == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
frame.recorded = false;
|
||||
std::vector<std::uint64_t> stamps(frame.used * 2);
|
||||
auto const result{vkGetQueryPoolResults(m_device, frame.pool, 0, frame.used * 2, stamps.size() * sizeof(std::uint64_t), stamps.data(), sizeof(std::uint64_t), VK_QUERY_RESULT_64_BIT)};
|
||||
if (result != VK_SUCCESS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// a running average, so the numbers can be read
|
||||
auto const blend = [](double &Average, double const Value) { Average = (Average <= 0.0 ? Value : Average * 0.9 + Value * 0.1); };
|
||||
double total{0.0};
|
||||
for (std::uint32_t i = 0; i < frame.used; ++i)
|
||||
{
|
||||
auto const ticks{((stamps[i * 2 + 1] & m_mask) - (stamps[i * 2] & m_mask)) & m_mask};
|
||||
auto const ms{static_cast<double>(ticks) * m_period / 1000000.0};
|
||||
total += ms;
|
||||
auto lookup{std::find_if(m_times.begin(), m_times.end(), [&](auto const &Entry) { return Entry.first == frame.names[i]; })};
|
||||
if (lookup == m_times.end())
|
||||
{
|
||||
m_times.emplace_back(frame.names[i], 0.0);
|
||||
lookup = std::prev(m_times.end());
|
||||
}
|
||||
blend(lookup->second, ms);
|
||||
}
|
||||
blend(m_framems, total);
|
||||
}
|
||||
|
||||
std::string pass_list::passes_of(std::uint64_t Number) const
|
||||
{
|
||||
std::string result;
|
||||
for (auto const &frame : m_frames)
|
||||
{
|
||||
if (frame.number != Number)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (auto const &name : frame.names)
|
||||
{
|
||||
result += (result.empty() ? "" : ", ") + name;
|
||||
}
|
||||
}
|
||||
return (result.empty() ? std::string("(not known)") : result);
|
||||
}
|
||||
|
||||
std::string pass_list::info() const
|
||||
{
|
||||
std::string text;
|
||||
char buffer[64] = {};
|
||||
for (auto const &[name, ms] : m_times)
|
||||
{
|
||||
std::snprintf(buffer, sizeof(buffer), " %-14s %7.3f ms\n", name.c_str(), ms);
|
||||
text += buffer;
|
||||
}
|
||||
std::snprintf(buffer, sizeof(buffer), " %-14s %7.3f ms\n", "total", m_framems);
|
||||
text += buffer;
|
||||
return text;
|
||||
}
|
||||
|
||||
} // namespace vk14
|
||||
96
rendering/vulkan14/vulkan14passes.h
Normal file
96
rendering/vulkan14/vulkan14passes.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// the passes of a frame (decision K12 of the plan): a list in the fixed order the frame makes them, each pass saying which images
|
||||
// it uses and how. the barriers between the passes (sync2) are worked out from the state each image was left in, there's no
|
||||
// graph. each pass is a label for the captures and the validation layer (debug utils), and is timed on the gpu: the times
|
||||
// of the passes of the frames done go to the debug panel under the same names as the parts of the gpu frame of the opengl
|
||||
// renderer. when the device is lost, the log tells which passes of which frame were on the gpu
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rendering/vulkan14/vulkan14common.h"
|
||||
#include "rendering/vulkan14/vulkan14memory.h"
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
class device;
|
||||
|
||||
// an image a pass uses, and how
|
||||
struct image_use
|
||||
{
|
||||
VkImage image{VK_NULL_HANDLE};
|
||||
VkImageAspectFlags aspect{VK_IMAGE_ASPECT_COLOR_BIT};
|
||||
// where the state of the image is kept: by the image itself, or by the swapchain for its images
|
||||
image_state *state{nullptr};
|
||||
VkImageLayout layout{VK_IMAGE_LAYOUT_UNDEFINED};
|
||||
VkPipelineStageFlags2 stage{VK_PIPELINE_STAGE_2_NONE};
|
||||
VkAccessFlags2 access{VK_ACCESS_2_NONE};
|
||||
};
|
||||
|
||||
// the use of a vk14::image
|
||||
image_use use_of(image &Image, VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access);
|
||||
|
||||
// the barriers which bring the images from their states to the uses, the states updated
|
||||
void barriers(VkCommandBuffer Commands, std::vector<image_use> const &Uses);
|
||||
|
||||
class pass_list
|
||||
{
|
||||
public:
|
||||
pass_list() = default;
|
||||
pass_list(pass_list const &) = delete;
|
||||
pass_list &operator=(pass_list const &) = delete;
|
||||
~pass_list();
|
||||
|
||||
bool create(device const &Device, std::uint32_t Frames, std::string &Reason);
|
||||
void destroy();
|
||||
|
||||
// the frame Frame (index of the frame in flight) is recorded to Commands from now on; Number counts the frames
|
||||
void begin_frame(VkCommandBuffer Commands, std::uint32_t Frame, std::uint64_t Number);
|
||||
// a pass: the barriers of its images, its label and the timestamp of its start
|
||||
void begin_pass(char const *Name, std::vector<image_use> const &Uses = {});
|
||||
void end_pass();
|
||||
void end_frame();
|
||||
// the gpu times of the frame Frame, once the gpu is done with it (after the wait for its value of the timeline)
|
||||
void collect(std::uint32_t Frame);
|
||||
|
||||
// the passes recorded in the frame done last, and the ones of the frame Number, for the report of a lost device
|
||||
std::string passes_of(std::uint64_t Number) const;
|
||||
// the gpu times (ms) of the parts of the frame, averaged, as the opengl renderer gives them: "name: x.xx ms" lines
|
||||
std::string info() const;
|
||||
double frame_ms() const
|
||||
{
|
||||
return m_framems;
|
||||
}
|
||||
|
||||
private:
|
||||
struct frame_queries
|
||||
{
|
||||
VkQueryPool pool{VK_NULL_HANDLE};
|
||||
std::vector<std::string> names;
|
||||
std::uint32_t used{0};
|
||||
std::uint64_t number{0};
|
||||
bool recorded{false};
|
||||
};
|
||||
static constexpr std::uint32_t max_passes{32};
|
||||
|
||||
VkDevice m_device{VK_NULL_HANDLE};
|
||||
float m_period{1.f}; // ns per tick
|
||||
std::uint64_t m_mask{~0ull}; // the valid bits of the timestamps
|
||||
std::vector<frame_queries> m_frames;
|
||||
VkCommandBuffer m_commands{VK_NULL_HANDLE};
|
||||
std::uint32_t m_frame{0};
|
||||
int m_open{-1};
|
||||
// averaged times of the parts, ms
|
||||
std::vector<std::pair<std::string, double>> m_times;
|
||||
double m_framems{0.0};
|
||||
};
|
||||
|
||||
} // namespace vk14
|
||||
@@ -13,20 +13,33 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "rendering/vulkan14/vulkan14instance.h"
|
||||
#include "rendering/vulkan14/vulkan14device.h"
|
||||
#include "rendering/vulkan14/vulkan14swapchain.h"
|
||||
#include "rendering/vulkan14/vulkan14memory.h"
|
||||
#include "rendering/vulkan14/vulkan14shaders.h"
|
||||
#include "rendering/vulkan14/vulkan14descriptors.h"
|
||||
#include "rendering/vulkan14/vulkan14passes.h"
|
||||
#include "rendering/vulkan14/vulkan14texture.h"
|
||||
#include "rendering/vulkan14/vulkan14imgui.h"
|
||||
#include "rendering/vulkan14/vulkan14bindings.h"
|
||||
#include "rendering/nullrenderer.h"
|
||||
#include "application/application.h"
|
||||
#include "simulation/simulation.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/Timer.h"
|
||||
#include "utilities/crashreporter.h"
|
||||
#include "utilities/utilities.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// the colour the windows are cleared to, the initial background of the opengl renderer (srgb)
|
||||
// the colour the windows are cleared to once a scenery is loaded, until the scenery is drawn (stage D3 of the plan): the initial
|
||||
// background of the opengl renderer (srgb). before that the windows are black, as with the opengl renderer
|
||||
glm::vec3 const clear_colour{51.f / 255.f, 102.f / 255.f, 85.f / 255.f};
|
||||
|
||||
// the ring each frame in flight streams its data through (the user interface for now)
|
||||
VkDeviceSize const ring_size{8 * 1024 * 1024};
|
||||
|
||||
float srgb_to_linear(float const Value)
|
||||
{
|
||||
return (Value <= 0.04045f ? Value / 12.92f : std::pow((Value + 0.055f) / 1.055f, 2.4f));
|
||||
@@ -40,13 +53,31 @@ struct vulkan14_renderer::context
|
||||
struct frame
|
||||
{
|
||||
VkCommandPool pool{VK_NULL_HANDLE};
|
||||
// the uploads of the frame go to the queue first, then the commands of the frame
|
||||
VkCommandBuffer uploads{VK_NULL_HANDLE};
|
||||
VkCommandBuffer commands{VK_NULL_HANDLE};
|
||||
// the value of the timeline semaphore the gpu reaches when it's done with the frame; 0: never submitted
|
||||
std::uint64_t done{0};
|
||||
// the number of the frame, for the report of a lost device
|
||||
std::uint64_t number{0};
|
||||
};
|
||||
|
||||
vk14::instance instance;
|
||||
vk14::device device;
|
||||
vk14::allocator memory;
|
||||
vk14::deletion_queue deletions;
|
||||
vk14::uploader uploads;
|
||||
vk14::image_table images;
|
||||
vk14::samplers sampling;
|
||||
vk14::frame_ring ring;
|
||||
vk14::shader_library shaders;
|
||||
vk14::pipeline_library pipelines;
|
||||
vk14::pass_list passes;
|
||||
vk14::pending_textures pending;
|
||||
vk14::resources resources;
|
||||
vk14::imgui_backend imgui;
|
||||
// the textures of the files; made after the objects above and gone before them
|
||||
std::unique_ptr<texture_bank> textures;
|
||||
// [0]: the main window, then the windows of the extra viewports
|
||||
std::vector<std::unique_ptr<vk14::swapchain>> windows;
|
||||
std::vector<frame> frames;
|
||||
@@ -94,21 +125,71 @@ struct vulkan14_renderer::context
|
||||
Reason = "no command pool";
|
||||
return false;
|
||||
}
|
||||
VkCommandBuffer buffers[2] = {VK_NULL_HANDLE, VK_NULL_HANDLE};
|
||||
VkCommandBufferAllocateInfo bufferinfo{};
|
||||
bufferinfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
bufferinfo.commandPool = frames[i].pool;
|
||||
bufferinfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
bufferinfo.commandBufferCount = 1;
|
||||
if (!VK14_VERIFY(vkAllocateCommandBuffers(handle, &bufferinfo, &frames[i].commands)))
|
||||
bufferinfo.commandBufferCount = 2;
|
||||
if (!VK14_VERIFY(vkAllocateCommandBuffers(handle, &bufferinfo, buffers)))
|
||||
{
|
||||
Reason = "no command buffer";
|
||||
return false;
|
||||
}
|
||||
frames[i].uploads = buffers[0];
|
||||
frames[i].commands = buffers[1];
|
||||
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].uploads), "uploads " + std::to_string(i));
|
||||
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].commands), "frame " + std::to_string(i));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// the parts of the renderer made once the device is there
|
||||
bool create_core(std::uint32_t const Frames, std::string &Reason)
|
||||
{
|
||||
if (false == memory.create(instance, device, Reason))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
uploads.init(memory, deletions, device.physical());
|
||||
if (false == sampling.create(device, static_cast<float>(Global.AnisotropicFiltering), Reason))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto const &limits{device.caps().limits};
|
||||
(void)limits;
|
||||
if (false == ring.create(memory, ring_size, Frames, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, "frame ring"))
|
||||
{
|
||||
Reason = "no ring for the data of the frames";
|
||||
return false;
|
||||
}
|
||||
if ((false == shaders.init(vk14::binding_preamble(), Reason)) || (false == pipelines.init(device, Reason)) || (false == passes.create(device, Frames, Reason)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
resources.gpu = &device;
|
||||
resources.memory = &memory;
|
||||
resources.uploads = &uploads;
|
||||
resources.deletions = &deletions;
|
||||
resources.sampling = &sampling;
|
||||
resources.images = &images;
|
||||
resources.thread = std::this_thread::get_id();
|
||||
resources.pending = &pending;
|
||||
textures = std::make_unique<texture_bank>([this]() -> texture_source * { return new vk14::vulkan14_texture(resources); });
|
||||
return imgui.create(resources, shaders, pipelines, ring, Reason);
|
||||
}
|
||||
|
||||
// the value of the timeline the gpu reached, 0 when it can't be read
|
||||
std::uint64_t completed() const
|
||||
{
|
||||
std::uint64_t value{0};
|
||||
if ((timeline == VK_NULL_HANDLE) || (vkGetSemaphoreCounterValue(device.handle(), timeline, &value) != VK_SUCCESS))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void destroy()
|
||||
{
|
||||
auto const handle{device.handle()};
|
||||
@@ -119,8 +200,20 @@ struct vulkan14_renderer::context
|
||||
}
|
||||
acquired.clear();
|
||||
windows.clear();
|
||||
imgui.destroy();
|
||||
// the textures give their images to the deletion queue, which goes now that the gpu is idle
|
||||
textures.reset();
|
||||
uploads.end_frame();
|
||||
uploads = vk14::uploader{};
|
||||
images.clear();
|
||||
deletions.flush();
|
||||
pipelines.shutdown();
|
||||
shaders.shutdown();
|
||||
passes.destroy();
|
||||
ring.destroy();
|
||||
if (handle != VK_NULL_HANDLE)
|
||||
{
|
||||
sampling.destroy(handle);
|
||||
for (auto &frame : frames)
|
||||
{
|
||||
if (frame.pool != VK_NULL_HANDLE)
|
||||
@@ -133,6 +226,7 @@ struct vulkan14_renderer::context
|
||||
vkDestroySemaphore(handle, timeline, nullptr);
|
||||
}
|
||||
}
|
||||
memory.destroy();
|
||||
frames.clear();
|
||||
timeline = VK_NULL_HANDLE;
|
||||
device.destroy();
|
||||
@@ -211,7 +305,8 @@ bool vulkan14_renderer::Init(GLFWwindow *Window)
|
||||
config.debug_utils = (Global.gfx_vulkan_validation > 0);
|
||||
|
||||
auto const framesinflight{static_cast<std::uint32_t>(std::clamp(Global.gfx_vulkan_framesinflight, 2, 3))};
|
||||
auto ok{ctx.instance.create(config, reason) && ctx.device.select(ctx.instance.handle(), VK_NULL_HANDLE, Global.gfx_vulkan_device, false, reason) && ctx.device.create(ctx.instance, reason) && ctx.create_frames(framesinflight, reason)};
|
||||
auto ok{ctx.instance.create(config, reason) && ctx.device.select(ctx.instance.handle(), VK_NULL_HANDLE, Global.gfx_vulkan_device, false, reason) && ctx.device.create(ctx.instance, reason) && ctx.create_frames(framesinflight, reason) &&
|
||||
ctx.create_core(framesinflight, reason)};
|
||||
if (ok)
|
||||
{
|
||||
auto mainwindow{std::make_unique<vk14::swapchain>()};
|
||||
@@ -227,7 +322,7 @@ bool vulkan14_renderer::Init(GLFWwindow *Window)
|
||||
|
||||
vk14::log_info(std::to_string(framesinflight) + " frames in flight");
|
||||
// until the stages of the plan bring them, these parts of the simulator aren't there with vulkan
|
||||
vk14::log_info("not available with vulkan yet: the scenery and the vehicles (D1-D7), the user interface (R5), picking, mouse position in the scene and screenshots (D6), "
|
||||
vk14::log_info("not available with vulkan yet: the scenery and the vehicles (D1-D7), picking, mouse position in the scene and screenshots (D6), the map panel, "
|
||||
"python screens in their own windows (N5), panels in their own windows (R5b), VR (X4)");
|
||||
WriteLog("Gfx Renderer: setup complete");
|
||||
update_info();
|
||||
@@ -271,6 +366,27 @@ void vulkan14_renderer::Shutdown()
|
||||
m_context.reset();
|
||||
}
|
||||
|
||||
void vulkan14_renderer::report_device_lost(char const *Where)
|
||||
{
|
||||
auto &ctx{*m_context};
|
||||
if (ctx.devicelost)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ctx.devicelost = true;
|
||||
auto const done{ctx.completed()};
|
||||
std::string report{std::string("the device was lost (") + Where + "); the gpu was done with the timeline value " + std::to_string(done) + " of " + std::to_string(ctx.timelinevalue)};
|
||||
for (auto const &frame : ctx.frames)
|
||||
{
|
||||
if ((frame.done > done) && (frame.done <= ctx.timelinevalue))
|
||||
{
|
||||
report += "; frame " + std::to_string(frame.number) + " (value " + std::to_string(frame.done) + ") wasn't done, its passes: " + ctx.passes.passes_of(frame.number);
|
||||
}
|
||||
}
|
||||
vk14::log_error(report);
|
||||
crashreport_add_info("vulkan_device_lost", report);
|
||||
}
|
||||
|
||||
bool vulkan14_renderer::Render()
|
||||
{
|
||||
if (!m_context || m_context->devicelost)
|
||||
@@ -294,11 +410,35 @@ bool vulkan14_renderer::Render()
|
||||
wait.pValues = &frame.done;
|
||||
if (!VK14_VERIFY(vkWaitSemaphores(device, &wait, UINT64_MAX)))
|
||||
{
|
||||
ctx.devicelost = true;
|
||||
report_device_lost("waiting for a frame");
|
||||
return false;
|
||||
}
|
||||
ctx.passes.collect(slot);
|
||||
}
|
||||
// what the frames done used last goes
|
||||
ctx.deletions.collect(std::max(ctx.completed(), frame.done));
|
||||
VK14_VERIFY(vkResetCommandPool(device, frame.pool, 0));
|
||||
ctx.ring.begin_frame(slot);
|
||||
|
||||
VkCommandBufferBeginInfo begin{};
|
||||
begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
VK14_VERIFY(vkBeginCommandBuffer(frame.uploads, &begin));
|
||||
VK14_VERIFY(vkBeginCommandBuffer(frame.commands, &begin));
|
||||
auto const value{ctx.timelinevalue + 1};
|
||||
ctx.uploads.begin_frame(frame.uploads, value);
|
||||
// the content other threads gave the textures (python)
|
||||
{
|
||||
std::vector<vk14::vulkan14_texture *> pending;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(ctx.pending.mutex);
|
||||
pending.swap(ctx.pending.textures);
|
||||
}
|
||||
for (auto *texture : pending)
|
||||
{
|
||||
texture->apply_pending();
|
||||
}
|
||||
}
|
||||
|
||||
ctx.acquired.clear();
|
||||
for (auto &window : ctx.windows)
|
||||
@@ -311,26 +451,61 @@ bool vulkan14_renderer::Render()
|
||||
case vk14::swapchain::acquire_result::skipped:
|
||||
break;
|
||||
default:
|
||||
ctx.devicelost = true;
|
||||
report_device_lost("acquiring an image");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// the frames of the user interface go on (its input works too), though nothing of it is drawn yet
|
||||
// the user interface: imgui makes its frame, the images it asks for are uploaded in this frame
|
||||
Timer::subsystem.gfx_gui.start();
|
||||
Application.render_ui();
|
||||
Timer::subsystem.gfx_gui.stop();
|
||||
|
||||
VkCommandBufferBeginInfo begin{};
|
||||
begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
VK14_VERIFY(vkBeginCommandBuffer(frame.commands, &begin));
|
||||
frame.number = ctx.framecount;
|
||||
ctx.passes.begin_frame(frame.commands, slot, ctx.framecount);
|
||||
auto const background{simulation::is_ready ? clear_colour : glm::vec3{0.f}};
|
||||
bool uidrawn{false};
|
||||
for (auto *window : ctx.acquired)
|
||||
{
|
||||
auto const colour{window->srgb() ? glm::vec3{srgb_to_linear(clear_colour.r), srgb_to_linear(clear_colour.g), srgb_to_linear(clear_colour.b)} : clear_colour};
|
||||
window->record_clear(frame.commands, glm::vec4{colour, 1.f});
|
||||
auto const colour{window->srgb() ? glm::vec3{srgb_to_linear(background.r), srgb_to_linear(background.g), srgb_to_linear(background.b)} : background};
|
||||
if (window != ctx.windows.front().get())
|
||||
{
|
||||
// the extra viewports: their colour only, until the scenery is drawn into them (D6)
|
||||
window->record_clear(frame.commands, glm::vec4{colour, 1.f});
|
||||
continue;
|
||||
}
|
||||
// the main window: cleared, the user interface over it, shown
|
||||
ctx.passes.begin_pass("ui", {window->use(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT)});
|
||||
VkRenderingAttachmentInfo attachment{};
|
||||
attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
|
||||
attachment.imageView = window->view();
|
||||
attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachment.clearValue.color = {{colour.r, colour.g, colour.b, 1.f}};
|
||||
VkRenderingInfo rendering{};
|
||||
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
|
||||
rendering.renderArea = {{0, 0}, window->extent()};
|
||||
rendering.layerCount = 1;
|
||||
rendering.colorAttachmentCount = 1;
|
||||
rendering.pColorAttachments = &attachment;
|
||||
vkCmdBeginRendering(frame.commands, &rendering);
|
||||
ctx.imgui.record(frame.commands, window->extent(), window->format(), window->srgb());
|
||||
vkCmdEndRendering(frame.commands);
|
||||
ctx.passes.end_pass();
|
||||
vk14::barriers(frame.commands, {window->use(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_PIPELINE_STAGE_2_NONE, VK_ACCESS_2_NONE)});
|
||||
uidrawn = true;
|
||||
}
|
||||
if (false == uidrawn)
|
||||
{
|
||||
// the main window is minimized: the frame of imgui has nowhere to go
|
||||
ctx.imgui.skip();
|
||||
}
|
||||
ctx.passes.end_frame();
|
||||
auto const uploaded{ctx.uploads.end_frame()};
|
||||
VK14_VERIFY(vkEndCommandBuffer(frame.uploads));
|
||||
VK14_VERIFY(vkEndCommandBuffer(frame.commands));
|
||||
ctx.ring.flush();
|
||||
|
||||
std::vector<VkSemaphoreSubmitInfo> waits;
|
||||
std::vector<VkSemaphoreSubmitInfo> signals;
|
||||
@@ -351,28 +526,40 @@ bool vulkan14_renderer::Render()
|
||||
VkSemaphoreSubmitInfo timelinesignal{};
|
||||
timelinesignal.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
|
||||
timelinesignal.semaphore = ctx.timeline;
|
||||
timelinesignal.value = ctx.timelinevalue + 1;
|
||||
timelinesignal.value = value;
|
||||
timelinesignal.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
|
||||
signals.push_back(timelinesignal);
|
||||
|
||||
VkCommandBufferSubmitInfo commands{};
|
||||
commands.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
|
||||
commands.commandBuffer = frame.commands;
|
||||
// the uploads first, in the same submit: their barriers make them done before the frame samples the images
|
||||
std::vector<VkCommandBufferSubmitInfo> commands;
|
||||
if (uploaded)
|
||||
{
|
||||
VkCommandBufferSubmitInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
|
||||
info.commandBuffer = frame.uploads;
|
||||
commands.push_back(info);
|
||||
}
|
||||
{
|
||||
VkCommandBufferSubmitInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
|
||||
info.commandBuffer = frame.commands;
|
||||
commands.push_back(info);
|
||||
}
|
||||
|
||||
VkSubmitInfo2 submit{};
|
||||
submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2;
|
||||
submit.waitSemaphoreInfoCount = static_cast<std::uint32_t>(waits.size());
|
||||
submit.pWaitSemaphoreInfos = waits.data();
|
||||
submit.commandBufferInfoCount = 1;
|
||||
submit.pCommandBufferInfos = &commands;
|
||||
submit.commandBufferInfoCount = static_cast<std::uint32_t>(commands.size());
|
||||
submit.pCommandBufferInfos = commands.data();
|
||||
submit.signalSemaphoreInfoCount = static_cast<std::uint32_t>(signals.size());
|
||||
submit.pSignalSemaphoreInfos = signals.data();
|
||||
if (!VK14_VERIFY(vkQueueSubmit2(ctx.device.graphics_queue(), 1, &submit, VK_NULL_HANDLE)))
|
||||
{
|
||||
ctx.devicelost = true;
|
||||
report_device_lost("submitting a frame");
|
||||
return false;
|
||||
}
|
||||
frame.done = ++ctx.timelinevalue;
|
||||
frame.done = ctx.timelinevalue = value;
|
||||
|
||||
Timer::subsystem.gfx_total.stop();
|
||||
return true;
|
||||
@@ -415,7 +602,7 @@ void vulkan14_renderer::SwapBuffers()
|
||||
if ((result == VK_ERROR_DEVICE_LOST) || (result == VK_ERROR_SURFACE_LOST_KHR))
|
||||
{
|
||||
vk14::log_error(std::string("vkQueuePresentKHR failed with ") + vk14::result_string(result));
|
||||
ctx.devicelost = true;
|
||||
report_device_lost("presenting");
|
||||
}
|
||||
ctx.acquired.clear();
|
||||
}
|
||||
@@ -475,7 +662,7 @@ gfx::userdata_array const &vulkan14_renderer::UserData(gfx::geometry_handle cons
|
||||
return m_geometry.userdata(Geometry);
|
||||
}
|
||||
|
||||
// materials, shaders, textures: none until D1 and D2
|
||||
// materials and their shaders: none until D1
|
||||
|
||||
material_handle vulkan14_renderer::Fetch_Material(std::string const &Filename, bool const Loadnow)
|
||||
{
|
||||
@@ -489,28 +676,44 @@ IMaterial const *vulkan14_renderer::Material(material_handle const Material) con
|
||||
return IMaterial::null_material();
|
||||
}
|
||||
|
||||
std::shared_ptr<gl::program> vulkan14_renderer::Fetch_Shader(std::string const &name)
|
||||
std::shared_ptr<gfx::shader_program> vulkan14_renderer::Fetch_Shader(std::string const &name)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// textures: the files read and decoded as for the opengl renderer, made into images when they are first used (create(), e.g. by the
|
||||
// launcher); the materials of the scene take them from D1
|
||||
|
||||
texture_handle vulkan14_renderer::Fetch_Texture(std::string const &Filename, bool const Loadnow, GLint format_hint)
|
||||
{
|
||||
return null_handle;
|
||||
if (!m_context || !m_context->textures)
|
||||
{
|
||||
return null_handle;
|
||||
}
|
||||
return m_context->textures->create(Filename, Loadnow, format_hint);
|
||||
}
|
||||
|
||||
// the shaders take the textures by the pushed descriptors (D1), there's nothing to bind
|
||||
void vulkan14_renderer::Bind_Texture(texture_handle const Texture) {}
|
||||
|
||||
void vulkan14_renderer::Bind_Texture(std::size_t const Unit, texture_handle const Texture) {}
|
||||
|
||||
ITexture &vulkan14_renderer::Texture(texture_handle const Texture)
|
||||
{
|
||||
return *ITexture::null_texture();
|
||||
if ((Texture == null_handle) || !m_context || !m_context->textures)
|
||||
{
|
||||
return *ITexture::null_texture();
|
||||
}
|
||||
return m_context->textures->mark_as_used(Texture);
|
||||
}
|
||||
|
||||
ITexture const &vulkan14_renderer::Texture(texture_handle const Texture) const
|
||||
{
|
||||
return *ITexture::null_texture();
|
||||
if ((Texture == null_handle) || !m_context || !m_context->textures)
|
||||
{
|
||||
return *ITexture::null_texture();
|
||||
}
|
||||
return m_context->textures->texture(Texture);
|
||||
}
|
||||
|
||||
// picking: D6
|
||||
@@ -538,6 +741,11 @@ glm::dvec3 vulkan14_renderer::Mouse_Position() const
|
||||
|
||||
void vulkan14_renderer::Update(double const Deltatime)
|
||||
{
|
||||
if (m_context && m_context->textures && (true == Global.ResourceSweep) && (true == simulation::is_ready))
|
||||
{
|
||||
// textures nothing used for a while give their images back
|
||||
m_context->textures->update();
|
||||
}
|
||||
m_updateaccumulator += Deltatime;
|
||||
if (m_updateaccumulator < 1.0)
|
||||
{
|
||||
@@ -559,7 +767,13 @@ void vulkan14_renderer::update_info()
|
||||
auto const &ctx{*m_context};
|
||||
auto const &caps{ctx.device.caps()};
|
||||
m_infotimes = "vulkan " + vk14::version_string(caps.api_version) + ": " + caps.device_name + " (" + caps.driver_name + ")\n" + "frames: " + std::to_string(ctx.framecount) + ", in flight " + std::to_string(ctx.frames.size()) + "\n" +
|
||||
"mainloop total: " + to_string(Timer::subsystem.mainloop_total.average(), 2) + " ms, frame cpu: " + to_string(Timer::subsystem.gfx_total.average(), 2) + " ms, present: " + to_string(Timer::subsystem.gfx_swap.average(), 2) + " ms\n";
|
||||
"mainloop total: " + to_string(Timer::subsystem.mainloop_total.average(), 2) + " ms, frame cpu: " + to_string(Timer::subsystem.gfx_total.average(), 2) + " ms, ui: " + to_string(Timer::subsystem.gfx_gui.average(), 2) +
|
||||
" ms, present: " + to_string(Timer::subsystem.gfx_swap.average(), 2) + " ms\n" + "gpu frame parts:\n" + ctx.passes.info();
|
||||
VkDeviceSize usage{0}, budget{0};
|
||||
ctx.memory.budget(usage, budget);
|
||||
m_infostats = "device memory: " + vk14::memory_size_string(usage) + " of " + vk14::memory_size_string(budget) + (ctx.device.memory_budget_enabled() ? "" : " (no budget from the driver)") + "\n" + "images: " + std::to_string(ctx.images.size()) +
|
||||
", uploaded " + vk14::memory_size_string(ctx.uploads.uploaded()) + ", waiting for the gpu to be deleted: " + std::to_string(ctx.deletions.size()) + "\n" + (ctx.textures ? ctx.textures->info() + "\n" : std::string{}) + ctx.shaders.info() +
|
||||
"\n" + ctx.pipelines.info() + "\n" + "frame ring: " + vk14::memory_size_string(ctx.ring.used()) + " of " + vk14::memory_size_string(ctx.ring.capacity()) + "\n";
|
||||
for (auto const &window : ctx.windows)
|
||||
{
|
||||
auto const extent{window->extent()};
|
||||
@@ -597,32 +811,10 @@ std::string const &vulkan14_renderer::info_stats() const
|
||||
|
||||
imgui_renderer *vulkan14_renderer::GetImguiRenderer()
|
||||
{
|
||||
return &m_imgui;
|
||||
return (m_context ? &m_context->imgui : nullptr);
|
||||
}
|
||||
|
||||
void vulkan14_renderer::MakeScreenshot()
|
||||
{
|
||||
vk14::log_warning("screenshots aren't made by the vulkan renderer yet (D6)");
|
||||
}
|
||||
|
||||
// imgui: the font atlas is built so its frames go on; nothing is drawn until R5
|
||||
|
||||
bool vulkan14_renderer::imgui_stub::Init()
|
||||
{
|
||||
auto &io{ImGui::GetIO()};
|
||||
io.BackendRendererName = "vulkan14 (no drawing yet)";
|
||||
unsigned char *pixels{nullptr};
|
||||
int width{0}, height{0};
|
||||
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
|
||||
io.Fonts->SetTexID(static_cast<ImTextureID>(0));
|
||||
return true;
|
||||
}
|
||||
|
||||
void vulkan14_renderer::imgui_stub::Shutdown()
|
||||
{
|
||||
ImGui::GetIO().BackendRendererName = nullptr;
|
||||
}
|
||||
|
||||
void vulkan14_renderer::imgui_stub::BeginFrame() {}
|
||||
|
||||
void vulkan14_renderer::imgui_stub::Render() {}
|
||||
|
||||
@@ -7,10 +7,11 @@ obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// the vulkan 1.4 renderer, picked with gfxrenderer vulkan. so far the frame of the renderer only: the gpu, the windows with
|
||||
// their swapchains, the frames in flight; each window is cleared to a colour. the scenery, the materials and the textures
|
||||
// come in the later stages of the plan (claude/vulkan14-renderer-plan.md); until then those methods keep the data the
|
||||
// simulation reads back and draw nothing.
|
||||
// the vulkan 1.4 renderer, picked with gfxrenderer vulkan. so far the core of the renderer (the track R of the plan,
|
||||
// claude/vulkan14-renderer-plan.md): the gpu, the windows with their swapchains, the frames in flight, the memory and the
|
||||
// uploads, the shaders and the pipelines, the pushed descriptors, the passes with their barriers and gpu times, the textures
|
||||
// and the user interface (imgui) with the launcher. the scenery and the materials come in the later stages of the plan; until
|
||||
// then those methods keep the data the simulation reads back and draw nothing.
|
||||
//
|
||||
// the vulkan headers stay in the files of rendering/vulkan14: the objects of the api live in vulkan14_renderer::context
|
||||
|
||||
@@ -46,7 +47,7 @@ class vulkan14_renderer : public gfx_renderer
|
||||
void Bind_Material(material_handle const Material, TSubModel const *sm = nullptr, lighting_data const *lighting = nullptr) override;
|
||||
IMaterial const *Material(material_handle const Material) const override;
|
||||
// shaders
|
||||
std::shared_ptr<gl::program> Fetch_Shader(std::string const &name) override;
|
||||
std::shared_ptr<gfx::shader_program> Fetch_Shader(std::string const &name) override;
|
||||
// textures
|
||||
texture_handle Fetch_Texture(std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA) override;
|
||||
void Bind_Texture(texture_handle const Texture) override;
|
||||
@@ -80,22 +81,13 @@ class vulkan14_renderer : public gfx_renderer
|
||||
// the objects of the vulkan api (vulkan14renderer.cpp)
|
||||
struct context;
|
||||
|
||||
// imgui without drawing: the frames of the ui go on (its input works), nothing is drawn until the stage R5 of the plan
|
||||
class imgui_stub : public imgui_renderer
|
||||
{
|
||||
public:
|
||||
bool Init() override;
|
||||
void Shutdown() override;
|
||||
void BeginFrame() override;
|
||||
void Render() override;
|
||||
};
|
||||
|
||||
void update_info();
|
||||
// the device was lost: what was on the gpu goes to the log
|
||||
void report_device_lost(char const *Where);
|
||||
|
||||
std::unique_ptr<context> m_context;
|
||||
// the data of the geometry, which the simulation reads back; nothing on the gpu yet
|
||||
gfx::geometrybank_manager m_geometry;
|
||||
imgui_stub m_imgui;
|
||||
float m_framerate{0.f};
|
||||
double m_updateaccumulator{0.0};
|
||||
bool m_debug_ui_active{false};
|
||||
|
||||
705
rendering/vulkan14/vulkan14shaders.cpp
Normal file
705
rendering/vulkan14/vulkan14shaders.cpp
Normal file
@@ -0,0 +1,705 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14shaders.h"
|
||||
#include "rendering/vulkan14/vulkan14device.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
#include <glslang/Public/ResourceLimits.h>
|
||||
#include <glslang/Public/ShaderLang.h>
|
||||
#include <SPIRV/GlslangToSpv.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
EShLanguage language_of(gfx::shader_stage const Stage)
|
||||
{
|
||||
switch (Stage)
|
||||
{
|
||||
case gfx::shader_stage::vertex:
|
||||
return EShLangVertex;
|
||||
case gfx::shader_stage::fragment:
|
||||
return EShLangFragment;
|
||||
case gfx::shader_stage::geometry:
|
||||
return EShLangGeometry;
|
||||
case gfx::shader_stage::compute:
|
||||
return EShLangCompute;
|
||||
}
|
||||
return EShLangVertex;
|
||||
}
|
||||
|
||||
VkShaderStageFlagBits stage_bit(gfx::shader_stage const Stage)
|
||||
{
|
||||
switch (Stage)
|
||||
{
|
||||
case gfx::shader_stage::vertex:
|
||||
return VK_SHADER_STAGE_VERTEX_BIT;
|
||||
case gfx::shader_stage::fragment:
|
||||
return VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
case gfx::shader_stage::geometry:
|
||||
return VK_SHADER_STAGE_GEOMETRY_BIT;
|
||||
case gfx::shader_stage::compute:
|
||||
return VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
}
|
||||
return VK_SHADER_STAGE_VERTEX_BIT;
|
||||
}
|
||||
|
||||
std::string files_of(gfx::shader_source const &Source)
|
||||
{
|
||||
std::string result;
|
||||
for (std::size_t i = 0; i < Source.files.size(); ++i)
|
||||
{
|
||||
result += (i > 0 ? ", " : "") + std::to_string(i) + ": " + Source.files[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> read_spirv(std::string const &Path)
|
||||
{
|
||||
std::ifstream file(Path, std::ios::binary | std::ios::ate);
|
||||
if (false == file.is_open())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
auto const size{static_cast<std::size_t>(file.tellg())};
|
||||
if ((size < 20) || (size % 4 != 0))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
std::vector<std::uint32_t> words(size / 4);
|
||||
file.seekg(0);
|
||||
file.read(reinterpret_cast<char *>(words.data()), static_cast<std::streamsize>(size));
|
||||
if ((false == static_cast<bool>(file)) || (words[0] != 0x07230203u))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
return words;
|
||||
}
|
||||
|
||||
// written beside and moved into place, so a program reading the cache at the same time doesn't get half of the file
|
||||
void write_file(std::string const &Path, void const *Data, std::size_t const Size)
|
||||
{
|
||||
std::error_code error;
|
||||
std::filesystem::create_directories(std::filesystem::path(Path).parent_path(), error);
|
||||
auto const temporary{Path + ".tmp"};
|
||||
{
|
||||
std::ofstream file(temporary, std::ios::binary | std::ios::trunc);
|
||||
if (false == file.is_open())
|
||||
{
|
||||
return;
|
||||
}
|
||||
file.write(static_cast<char const *>(Data), static_cast<std::streamsize>(Size));
|
||||
if (false == static_cast<bool>(file))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::filesystem::rename(temporary, Path, error);
|
||||
if (error)
|
||||
{
|
||||
std::filesystem::remove(temporary, error);
|
||||
}
|
||||
}
|
||||
|
||||
std::string hex(std::uint64_t const Value)
|
||||
{
|
||||
char buffer[17] = {};
|
||||
std::snprintf(buffer, sizeof(buffer), "%016llx", static_cast<unsigned long long>(Value));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// fnv-1a 64 over more data
|
||||
void hash_bytes(std::uint64_t &Hash, void const *Data, std::size_t const Size)
|
||||
{
|
||||
auto const *bytes{static_cast<std::uint8_t const *>(Data)};
|
||||
for (std::size_t i = 0; i < Size; ++i)
|
||||
{
|
||||
Hash ^= bytes[i];
|
||||
Hash *= 1099511628211ull;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Type_> void hash_value(std::uint64_t &Hash, Type_ const &Value)
|
||||
{
|
||||
hash_bytes(Hash, &Value, sizeof(Value));
|
||||
}
|
||||
|
||||
std::string user_cache_dir(std::string const &Name)
|
||||
{
|
||||
// with a separator at the end
|
||||
auto const path{user_config_path("cache")};
|
||||
auto const name{Name.empty() ? std::string{} : Name + "/"};
|
||||
if (path.empty())
|
||||
{
|
||||
return "cache/vulkan14/" + name;
|
||||
}
|
||||
return (path / "vulkan14").string() + "/" + name;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// shader library
|
||||
|
||||
shader_library::~shader_library()
|
||||
{
|
||||
shutdown();
|
||||
}
|
||||
|
||||
bool shader_library::init(std::string const &Preamble, std::string &Reason)
|
||||
{
|
||||
m_preamble = Preamble;
|
||||
if (false == glslang::InitializeProcess())
|
||||
{
|
||||
Reason = "the shader compiler (glslang) can't start";
|
||||
return false;
|
||||
}
|
||||
m_glslang = true;
|
||||
auto const version{glslang::GetVersion()};
|
||||
m_compilerversion = "glslang " + std::to_string(version.major) + "." + std::to_string(version.minor) + "." + std::to_string(version.patch) + " vulkan1.4 spirv1.6";
|
||||
m_cachedir = user_cache_dir("shaders");
|
||||
|
||||
m_binaries = Global.gfx_shader_binaries;
|
||||
if (m_binaries)
|
||||
{
|
||||
// the binaries are compiled with the constants written in the files, the sources get those of the renderer
|
||||
std::vector<std::string> differences;
|
||||
if (false == gfx::shader_constants_match(gfx::shader_api::vulkan14, "common.glsl", Preamble, {}, differences))
|
||||
{
|
||||
std::string text;
|
||||
for (auto const &difference : differences)
|
||||
{
|
||||
text += "\n " + difference;
|
||||
}
|
||||
ErrorLog("vulkan: the constants in " + gfx::shader_source_dir(gfx::shader_api::vulkan14) + "common.glsl aren't those of the renderer, so the SPIR-V binaries of " +
|
||||
gfx::shader_binary_dir(gfx::shader_api::vulkan14) + " can't be used; the shaders are compiled from the sources:" + text,
|
||||
logtype::shader);
|
||||
m_binaries = false;
|
||||
}
|
||||
}
|
||||
log_info("shaders: " + std::string(m_binaries ? "the SPIR-V binaries of " + gfx::shader_binary_dir(gfx::shader_api::vulkan14) + " first, then " : "") + "the cache in " + m_cachedir + ", then " + m_compilerversion);
|
||||
return true;
|
||||
}
|
||||
|
||||
void shader_library::shutdown()
|
||||
{
|
||||
m_shaders.clear();
|
||||
if (m_glslang)
|
||||
{
|
||||
glslang::FinalizeProcess();
|
||||
m_glslang = false;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<shader_code const> shader_library::get(std::string const &Name, std::string const &Defines)
|
||||
{
|
||||
auto const key{Name + "|" + Defines};
|
||||
auto const lookup{m_shaders.find(key)};
|
||||
if (lookup != m_shaders.end())
|
||||
{
|
||||
return lookup->second;
|
||||
}
|
||||
|
||||
auto code{std::make_shared<shader_code>()};
|
||||
code->source = gfx::preprocess_shader(gfx::shader_api::vulkan14, Name, Defines, m_preamble, true);
|
||||
code->stage = stage_bit(code->source.stage);
|
||||
auto const label{Name + (code->source.variant.empty() ? "" : " (" + code->source.variant + ")")};
|
||||
|
||||
if (m_binaries)
|
||||
{
|
||||
std::string reason;
|
||||
code->spirv = gfx::load_shader_binary(code->source, reason);
|
||||
if (false == reason.empty())
|
||||
{
|
||||
log_info("shader " + label + ": " + reason + ", compiled from the source");
|
||||
}
|
||||
if (false == code->spirv.empty())
|
||||
{
|
||||
code->origin = "binary";
|
||||
++m_frombinary;
|
||||
}
|
||||
}
|
||||
if (code->spirv.empty())
|
||||
{
|
||||
if (false == code->source.has_source)
|
||||
{
|
||||
throw gfx::shader_exception("cannot read shader: " + gfx::shader_source_dir(gfx::shader_api::vulkan14) + Name);
|
||||
}
|
||||
std::uint64_t hash{14695981039346656037ull};
|
||||
hash_bytes(hash, code->source.text.data(), code->source.text.size());
|
||||
hash_bytes(hash, m_compilerversion.data(), m_compilerversion.size());
|
||||
auto const cachepath{m_cachedir + Name + (code->source.variant.empty() ? "" : "." + code->source.variant) + "." + hex(hash) + ".spv"};
|
||||
code->spirv = read_spirv(cachepath);
|
||||
if (false == code->spirv.empty())
|
||||
{
|
||||
code->origin = "cache";
|
||||
++m_fromcache;
|
||||
}
|
||||
else
|
||||
{
|
||||
code->spirv = compile(code->source);
|
||||
code->origin = "compiled";
|
||||
++m_compiled;
|
||||
write_file(cachepath, code->spirv.data(), code->spirv.size() * sizeof(std::uint32_t));
|
||||
}
|
||||
}
|
||||
// the text isn't needed any more
|
||||
code->source.text = std::string();
|
||||
m_shaders.emplace(key, code);
|
||||
return code;
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> shader_library::compile(gfx::shader_source const &Source) const
|
||||
{
|
||||
auto const language{language_of(Source.stage)};
|
||||
glslang::TShader shader(language);
|
||||
char const *text{Source.text.c_str()};
|
||||
char const *name{Source.name.c_str()};
|
||||
shader.setStringsWithLengthsAndNames(&text, nullptr, &name, 1);
|
||||
shader.setEnvInput(glslang::EShSourceGlsl, language, glslang::EShClientVulkan, 100);
|
||||
shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_4);
|
||||
shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_6);
|
||||
auto const messages{static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules)};
|
||||
if (false == shader.parse(GetDefaultResources(), 460, false, messages))
|
||||
{
|
||||
auto const info{std::string(shader.getInfoLog())};
|
||||
ErrorLog("bad shader: " + Source.name + ": " + info + "(source strings: " + files_of(Source) + ")", logtype::shader);
|
||||
throw gfx::shader_exception("failed to compile " + Source.name + ": " + info);
|
||||
}
|
||||
glslang::TProgram program;
|
||||
program.addShader(&shader);
|
||||
if (false == program.link(messages))
|
||||
{
|
||||
auto const info{std::string(program.getInfoLog())};
|
||||
ErrorLog("bad shader: " + Source.name + ": " + info, logtype::shader);
|
||||
throw gfx::shader_exception("failed to link " + Source.name + ": " + info);
|
||||
}
|
||||
std::vector<std::uint32_t> spirv;
|
||||
glslang::SpvOptions options;
|
||||
options.validate = false;
|
||||
glslang::GlslangToSpv(*program.getIntermediate(language), spirv, &options);
|
||||
if (spirv.empty())
|
||||
{
|
||||
throw gfx::shader_exception("no SPIR-V made of " + Source.name);
|
||||
}
|
||||
return spirv;
|
||||
}
|
||||
|
||||
std::string shader_library::info() const
|
||||
{
|
||||
return "shaders: " + std::to_string(m_shaders.size()) + " (" + std::to_string(m_frombinary) + " binaries, " + std::to_string(m_fromcache) + " from the cache, " + std::to_string(m_compiled) + " compiled)";
|
||||
}
|
||||
|
||||
// pipeline library
|
||||
|
||||
pipeline_library::~pipeline_library()
|
||||
{
|
||||
shutdown();
|
||||
}
|
||||
|
||||
bool pipeline_library::init(device const &Device, std::string &Reason)
|
||||
{
|
||||
m_device = &Device;
|
||||
auto const cachedir{user_cache_dir("")};
|
||||
m_cachepath = cachedir + "pipelines.bin";
|
||||
|
||||
// the cache of another device or driver is no good; vulkan would ignore it, but it would be written over anyway
|
||||
std::vector<char> data;
|
||||
{
|
||||
std::ifstream file(m_cachepath, std::ios::binary | std::ios::ate);
|
||||
if (file.is_open())
|
||||
{
|
||||
data.resize(static_cast<std::size_t>(file.tellg()));
|
||||
file.seekg(0);
|
||||
file.read(data.data(), static_cast<std::streamsize>(data.size()));
|
||||
if (false == static_cast<bool>(file))
|
||||
{
|
||||
data.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (false == data.empty())
|
||||
{
|
||||
VkPipelineCacheHeaderVersionOne header{};
|
||||
auto const &caps{Device.caps()};
|
||||
bool valid{data.size() >= sizeof(header)};
|
||||
if (valid)
|
||||
{
|
||||
std::memcpy(&header, data.data(), sizeof(header));
|
||||
valid = (header.headerSize >= sizeof(header)) && (header.headerVersion == VK_PIPELINE_CACHE_HEADER_VERSION_ONE) && (header.vendorID == caps.vendor_id) && (header.deviceID == caps.device_id) &&
|
||||
(std::memcmp(header.pipelineCacheUUID, caps.pipeline_cache_uuid.data(), VK_UUID_SIZE) == 0);
|
||||
}
|
||||
if (false == valid)
|
||||
{
|
||||
log_info("the pipeline cache " + m_cachepath + " comes from another device or driver, made anew");
|
||||
data.clear();
|
||||
}
|
||||
}
|
||||
|
||||
VkPipelineCacheCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
|
||||
info.initialDataSize = data.size();
|
||||
info.pInitialData = (data.empty() ? nullptr : data.data());
|
||||
if (!VK14_VERIFY(vkCreatePipelineCache(Device.handle(), &info, nullptr, &m_cache)))
|
||||
{
|
||||
// without the old data, then
|
||||
info.initialDataSize = 0;
|
||||
info.pInitialData = nullptr;
|
||||
if (!VK14_VERIFY(vkCreatePipelineCache(Device.handle(), &info, nullptr, &m_cache)))
|
||||
{
|
||||
m_cache = VK_NULL_HANDLE;
|
||||
Reason = "no pipeline cache";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
log_info("pipeline cache " + m_cachepath + (data.empty() ? " (empty)" : " (" + std::to_string(data.size() / 1024) + " KiB)"));
|
||||
return true;
|
||||
}
|
||||
|
||||
void pipeline_library::shutdown()
|
||||
{
|
||||
if (m_device == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto const device{m_device->handle()};
|
||||
if (device != VK_NULL_HANDLE)
|
||||
{
|
||||
for (auto const &[key, pipeline] : m_pipelines)
|
||||
{
|
||||
if (pipeline != VK_NULL_HANDLE)
|
||||
{
|
||||
vkDestroyPipeline(device, pipeline, nullptr);
|
||||
}
|
||||
}
|
||||
if (m_cache != VK_NULL_HANDLE)
|
||||
{
|
||||
std::size_t size{0};
|
||||
if (VK14_VERIFY(vkGetPipelineCacheData(device, m_cache, &size, nullptr)) && (size > 0))
|
||||
{
|
||||
std::vector<char> data(size);
|
||||
if (VK14_VERIFY(vkGetPipelineCacheData(device, m_cache, &size, data.data())))
|
||||
{
|
||||
write_file(m_cachepath, data.data(), size);
|
||||
}
|
||||
}
|
||||
vkDestroyPipelineCache(device, m_cache, nullptr);
|
||||
}
|
||||
}
|
||||
m_pipelines.clear();
|
||||
m_cache = VK_NULL_HANDLE;
|
||||
m_device = nullptr;
|
||||
}
|
||||
|
||||
std::uint64_t pipeline_library::key_of(pipeline_desc const &Desc) const
|
||||
{
|
||||
std::uint64_t hash{14695981039346656037ull};
|
||||
for (auto const &stage : Desc.stages)
|
||||
{
|
||||
hash_value(hash, stage.get());
|
||||
}
|
||||
hash_value(hash, Desc.layout);
|
||||
for (auto const &binding : Desc.vertices.bindings)
|
||||
{
|
||||
hash_value(hash, binding.binding);
|
||||
hash_value(hash, binding.stride);
|
||||
hash_value(hash, binding.inputRate);
|
||||
}
|
||||
for (auto const &attribute : Desc.vertices.attributes)
|
||||
{
|
||||
hash_value(hash, attribute.location);
|
||||
hash_value(hash, attribute.binding);
|
||||
hash_value(hash, attribute.format);
|
||||
hash_value(hash, attribute.offset);
|
||||
}
|
||||
hash_value(hash, Desc.topology);
|
||||
hash_value(hash, Desc.blending);
|
||||
for (auto const format : Desc.colour_formats)
|
||||
{
|
||||
hash_value(hash, format);
|
||||
}
|
||||
hash_value(hash, Desc.depth_format);
|
||||
hash_value(hash, Desc.samples);
|
||||
for (auto const value : Desc.specialization)
|
||||
{
|
||||
hash_value(hash, value);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
VkPrimitiveTopology topology_class_head(VkPrimitiveTopology const Topology)
|
||||
{
|
||||
return Topology;
|
||||
}
|
||||
|
||||
std::vector<VkDynamicState> const dynamic_states{VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR,
|
||||
VK_DYNAMIC_STATE_CULL_MODE,
|
||||
VK_DYNAMIC_STATE_FRONT_FACE,
|
||||
VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY,
|
||||
VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE,
|
||||
VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE,
|
||||
VK_DYNAMIC_STATE_DEPTH_COMPARE_OP,
|
||||
VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE,
|
||||
VK_DYNAMIC_STATE_DEPTH_BIAS,
|
||||
VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE,
|
||||
VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE};
|
||||
|
||||
} // namespace
|
||||
|
||||
VkPipeline pipeline_library::get(pipeline_desc const &Desc)
|
||||
{
|
||||
auto const key{key_of(Desc)};
|
||||
auto const lookup{m_pipelines.find(key)};
|
||||
if (lookup != m_pipelines.end())
|
||||
{
|
||||
return lookup->second;
|
||||
}
|
||||
auto const start{std::chrono::steady_clock::now()};
|
||||
|
||||
// the code goes straight to the stages (maintenance5), with the specialization of all of them
|
||||
std::vector<VkSpecializationMapEntry> entries(Desc.specialization.size());
|
||||
for (std::size_t i = 0; i < entries.size(); ++i)
|
||||
{
|
||||
entries[i] = {static_cast<std::uint32_t>(i), static_cast<std::uint32_t>(i * sizeof(std::uint32_t)), sizeof(std::uint32_t)};
|
||||
}
|
||||
VkSpecializationInfo specialization{};
|
||||
specialization.mapEntryCount = static_cast<std::uint32_t>(entries.size());
|
||||
specialization.pMapEntries = entries.data();
|
||||
specialization.dataSize = Desc.specialization.size() * sizeof(std::uint32_t);
|
||||
specialization.pData = Desc.specialization.data();
|
||||
|
||||
std::vector<VkShaderModuleCreateInfo> modules(Desc.stages.size());
|
||||
std::vector<VkPipelineShaderStageCreateInfo> stages(Desc.stages.size());
|
||||
for (std::size_t i = 0; i < Desc.stages.size(); ++i)
|
||||
{
|
||||
modules[i].sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
modules[i].codeSize = Desc.stages[i]->spirv.size() * sizeof(std::uint32_t);
|
||||
modules[i].pCode = Desc.stages[i]->spirv.data();
|
||||
stages[i].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
stages[i].pNext = &modules[i];
|
||||
stages[i].stage = Desc.stages[i]->stage;
|
||||
stages[i].module = VK_NULL_HANDLE;
|
||||
stages[i].pName = "main";
|
||||
stages[i].pSpecializationInfo = (entries.empty() ? nullptr : &specialization);
|
||||
}
|
||||
|
||||
VkPipelineVertexInputStateCreateInfo vertexinput{};
|
||||
vertexinput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
||||
vertexinput.vertexBindingDescriptionCount = static_cast<std::uint32_t>(Desc.vertices.bindings.size());
|
||||
vertexinput.pVertexBindingDescriptions = Desc.vertices.bindings.data();
|
||||
vertexinput.vertexAttributeDescriptionCount = static_cast<std::uint32_t>(Desc.vertices.attributes.size());
|
||||
vertexinput.pVertexAttributeDescriptions = Desc.vertices.attributes.data();
|
||||
|
||||
VkPipelineInputAssemblyStateCreateInfo inputassembly{};
|
||||
inputassembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
||||
inputassembly.topology = topology_class_head(Desc.topology);
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewport{};
|
||||
viewport.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
||||
viewport.viewportCount = 1;
|
||||
viewport.scissorCount = 1;
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo rasterization{};
|
||||
rasterization.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
|
||||
rasterization.polygonMode = VK_POLYGON_MODE_FILL;
|
||||
rasterization.cullMode = VK_CULL_MODE_NONE;
|
||||
rasterization.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
rasterization.lineWidth = 1.f;
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo multisample{};
|
||||
multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
||||
multisample.rasterizationSamples = Desc.samples;
|
||||
|
||||
VkPipelineDepthStencilStateCreateInfo depthstencil{};
|
||||
depthstencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
|
||||
depthstencil.depthCompareOp = VK_COMPARE_OP_GREATER_OR_EQUAL;
|
||||
depthstencil.maxDepthBounds = 1.f;
|
||||
|
||||
std::vector<VkPipelineColorBlendAttachmentState> attachments(Desc.colour_formats.size());
|
||||
for (auto &attachment : attachments)
|
||||
{
|
||||
attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||
switch (Desc.blending)
|
||||
{
|
||||
case blend_mode::none:
|
||||
break;
|
||||
case blend_mode::alpha:
|
||||
attachment.blendEnable = VK_TRUE;
|
||||
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
attachment.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
break;
|
||||
case blend_mode::premultiplied:
|
||||
attachment.blendEnable = VK_TRUE;
|
||||
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
attachment.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
break;
|
||||
case blend_mode::additive:
|
||||
attachment.blendEnable = VK_TRUE;
|
||||
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
attachment.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
break;
|
||||
}
|
||||
}
|
||||
VkPipelineColorBlendStateCreateInfo blend{};
|
||||
blend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
|
||||
blend.attachmentCount = static_cast<std::uint32_t>(attachments.size());
|
||||
blend.pAttachments = attachments.data();
|
||||
|
||||
VkPipelineDynamicStateCreateInfo dynamic{};
|
||||
dynamic.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
||||
dynamic.dynamicStateCount = static_cast<std::uint32_t>(dynamic_states.size());
|
||||
dynamic.pDynamicStates = dynamic_states.data();
|
||||
|
||||
// no render pass: the formats of the attachments only (dynamic rendering)
|
||||
VkPipelineRenderingCreateInfo rendering{};
|
||||
rendering.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
|
||||
rendering.colorAttachmentCount = static_cast<std::uint32_t>(Desc.colour_formats.size());
|
||||
rendering.pColorAttachmentFormats = Desc.colour_formats.data();
|
||||
rendering.depthAttachmentFormat = Desc.depth_format;
|
||||
|
||||
VkGraphicsPipelineCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
||||
info.pNext = &rendering;
|
||||
info.stageCount = static_cast<std::uint32_t>(stages.size());
|
||||
info.pStages = stages.data();
|
||||
info.pVertexInputState = &vertexinput;
|
||||
info.pInputAssemblyState = &inputassembly;
|
||||
info.pViewportState = &viewport;
|
||||
info.pRasterizationState = &rasterization;
|
||||
info.pMultisampleState = &multisample;
|
||||
info.pDepthStencilState = &depthstencil;
|
||||
info.pColorBlendState = &blend;
|
||||
info.pDynamicState = &dynamic;
|
||||
info.layout = Desc.layout;
|
||||
|
||||
VkPipeline pipeline{VK_NULL_HANDLE};
|
||||
if (!VK14_VERIFY(vkCreateGraphicsPipelines(m_device->handle(), m_cache, 1, &info, nullptr, &pipeline)))
|
||||
{
|
||||
log_error("the pipeline " + Desc.name + " can't be made");
|
||||
pipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
name_object(m_device->handle(), VK_OBJECT_TYPE_PIPELINE, handle_value(pipeline), Desc.name);
|
||||
}
|
||||
m_creationms += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
||||
m_pipelines.emplace(key, pipeline);
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
VkPipeline pipeline_library::compute(std::shared_ptr<shader_code const> const &Shader, VkPipelineLayout Layout, std::vector<std::uint32_t> const &Specialization, std::string const &Name)
|
||||
{
|
||||
std::uint64_t key{14695981039346656037ull};
|
||||
hash_value(key, Shader.get());
|
||||
hash_value(key, Layout);
|
||||
for (auto const value : Specialization)
|
||||
{
|
||||
hash_value(key, value);
|
||||
}
|
||||
auto const lookup{m_pipelines.find(key)};
|
||||
if (lookup != m_pipelines.end())
|
||||
{
|
||||
return lookup->second;
|
||||
}
|
||||
auto const start{std::chrono::steady_clock::now()};
|
||||
|
||||
std::vector<VkSpecializationMapEntry> entries(Specialization.size());
|
||||
for (std::size_t i = 0; i < entries.size(); ++i)
|
||||
{
|
||||
entries[i] = {static_cast<std::uint32_t>(i), static_cast<std::uint32_t>(i * sizeof(std::uint32_t)), sizeof(std::uint32_t)};
|
||||
}
|
||||
VkSpecializationInfo specialization{};
|
||||
specialization.mapEntryCount = static_cast<std::uint32_t>(entries.size());
|
||||
specialization.pMapEntries = entries.data();
|
||||
specialization.dataSize = Specialization.size() * sizeof(std::uint32_t);
|
||||
specialization.pData = Specialization.data();
|
||||
|
||||
VkShaderModuleCreateInfo module{};
|
||||
module.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
module.codeSize = Shader->spirv.size() * sizeof(std::uint32_t);
|
||||
module.pCode = Shader->spirv.data();
|
||||
|
||||
VkComputePipelineCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
|
||||
info.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
info.stage.pNext = &module;
|
||||
info.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
info.stage.pName = "main";
|
||||
info.stage.pSpecializationInfo = (entries.empty() ? nullptr : &specialization);
|
||||
info.layout = Layout;
|
||||
|
||||
VkPipeline pipeline{VK_NULL_HANDLE};
|
||||
if (!VK14_VERIFY(vkCreateComputePipelines(m_device->handle(), m_cache, 1, &info, nullptr, &pipeline)))
|
||||
{
|
||||
log_error("the compute pipeline " + Name + " can't be made");
|
||||
pipeline = VK_NULL_HANDLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
name_object(m_device->handle(), VK_OBJECT_TYPE_PIPELINE, handle_value(pipeline), Name);
|
||||
}
|
||||
m_creationms += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
|
||||
m_pipelines.emplace(key, pipeline);
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
std::string pipeline_library::info() const
|
||||
{
|
||||
char buffer[32] = {};
|
||||
std::snprintf(buffer, sizeof(buffer), "%.1f", m_creationms);
|
||||
return "pipelines: " + std::to_string(m_pipelines.size()) + " (made in " + buffer + " ms)";
|
||||
}
|
||||
|
||||
void set_draw_state(VkCommandBuffer Commands, VkExtent2D Extent)
|
||||
{
|
||||
VkViewport viewport{0.f, 0.f, static_cast<float>(Extent.width), static_cast<float>(Extent.height), 0.f, 1.f};
|
||||
vkCmdSetViewport(Commands, 0, 1, &viewport);
|
||||
VkRect2D scissor{{0, 0}, Extent};
|
||||
vkCmdSetScissor(Commands, 0, 1, &scissor);
|
||||
vkCmdSetCullMode(Commands, VK_CULL_MODE_NONE);
|
||||
vkCmdSetFrontFace(Commands, VK_FRONT_FACE_COUNTER_CLOCKWISE);
|
||||
vkCmdSetPrimitiveTopology(Commands, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
|
||||
vkCmdSetDepthTestEnable(Commands, VK_FALSE);
|
||||
vkCmdSetDepthWriteEnable(Commands, VK_FALSE);
|
||||
// reverse z, as the opengl renderer
|
||||
vkCmdSetDepthCompareOp(Commands, VK_COMPARE_OP_GREATER_OR_EQUAL);
|
||||
vkCmdSetDepthBiasEnable(Commands, VK_FALSE);
|
||||
vkCmdSetDepthBias(Commands, 0.f, 0.f, 0.f);
|
||||
vkCmdSetPrimitiveRestartEnable(Commands, VK_FALSE);
|
||||
vkCmdSetStencilTestEnable(Commands, VK_FALSE);
|
||||
}
|
||||
|
||||
} // namespace vk14
|
||||
134
rendering/vulkan14/vulkan14shaders.h
Normal file
134
rendering/vulkan14/vulkan14shaders.h
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// the shaders and the pipelines of the vulkan renderer.
|
||||
//
|
||||
// a shader of shaders/vk14 is taken from the SPIR-V binary of shaders/vk14bin when there's an up to date one
|
||||
// (gfx.shaders.binaries), from the cache of the shaders compiled before (cache/vulkan14/shaders in the directory of the user,
|
||||
// keyed by the source with its includes, the variant and the version of glslang), or compiled now by glslang and put in the
|
||||
// cache. the pipelines are made from a description of what isn't dynamic (the shaders, the layout of the vertices, the class
|
||||
// of the primitives, the blending, the formats of the attachments, the samples); the rest of the state is dynamic. the
|
||||
// VkPipelineCache is read from and written to the directory of the user, and thrown away when it comes from another device
|
||||
// or driver. no VkShaderModule: the code goes to the stages of the pipelines (maintenance5)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rendering/vulkan14/vulkan14common.h"
|
||||
#include "rendering/common/shadersource.h"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
class device;
|
||||
|
||||
// the SPIR-V of a shader, and what its source says (the material directives)
|
||||
struct shader_code
|
||||
{
|
||||
gfx::shader_source source;
|
||||
VkShaderStageFlagBits stage{VK_SHADER_STAGE_VERTEX_BIT};
|
||||
std::vector<std::uint32_t> spirv;
|
||||
// where it came from: "binary", "cache", "compiled"
|
||||
char const *origin{""};
|
||||
};
|
||||
|
||||
class shader_library
|
||||
{
|
||||
public:
|
||||
shader_library() = default;
|
||||
shader_library(shader_library const &) = delete;
|
||||
shader_library &operator=(shader_library const &) = delete;
|
||||
~shader_library();
|
||||
|
||||
// Preamble: what the renderer puts after #version (the constants of vulkan14bindings.h)
|
||||
bool init(std::string const &Preamble, std::string &Reason);
|
||||
void shutdown();
|
||||
// the shader of shaders/vk14 (Defines: the variant). throws gfx::shader_exception
|
||||
std::shared_ptr<shader_code const> get(std::string const &Name, std::string const &Defines = {});
|
||||
// what was loaded from where, for the debug panel
|
||||
std::string info() const;
|
||||
|
||||
private:
|
||||
std::vector<std::uint32_t> compile(gfx::shader_source const &Source) const;
|
||||
|
||||
std::string m_preamble;
|
||||
std::string m_cachedir;
|
||||
std::string m_compilerversion;
|
||||
bool m_glslang{false};
|
||||
bool m_binaries{false};
|
||||
std::unordered_map<std::string, std::shared_ptr<shader_code const>> m_shaders;
|
||||
int m_frombinary{0};
|
||||
int m_fromcache{0};
|
||||
int m_compiled{0};
|
||||
};
|
||||
|
||||
// how the colour of a pipeline is mixed with what is there
|
||||
enum class blend_mode
|
||||
{
|
||||
none,
|
||||
alpha, // the colour over what is there by its alpha, the alpha kept for what's behind (the user interface)
|
||||
premultiplied,
|
||||
additive
|
||||
};
|
||||
|
||||
struct vertex_layout
|
||||
{
|
||||
std::vector<VkVertexInputBindingDescription> bindings;
|
||||
std::vector<VkVertexInputAttributeDescription> attributes;
|
||||
};
|
||||
|
||||
// what a pipeline is made of; the rest of the state is set while drawing (vk14::set_draw_state())
|
||||
struct pipeline_desc
|
||||
{
|
||||
std::vector<std::shared_ptr<shader_code const>> stages;
|
||||
VkPipelineLayout layout{VK_NULL_HANDLE};
|
||||
vertex_layout vertices;
|
||||
// the class of the primitives; the primitive itself is dynamic
|
||||
VkPrimitiveTopology topology{VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST};
|
||||
blend_mode blending{blend_mode::none};
|
||||
std::vector<VkFormat> colour_formats;
|
||||
VkFormat depth_format{VK_FORMAT_UNDEFINED};
|
||||
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
|
||||
// the values of the specialization constants 0, 1, 2 ... of all the stages
|
||||
std::vector<std::uint32_t> specialization;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
class pipeline_library
|
||||
{
|
||||
public:
|
||||
pipeline_library() = default;
|
||||
pipeline_library(pipeline_library const &) = delete;
|
||||
pipeline_library &operator=(pipeline_library const &) = delete;
|
||||
~pipeline_library();
|
||||
|
||||
bool init(device const &Device, std::string &Reason);
|
||||
// writes the pipeline cache to the disk and destroys the pipelines
|
||||
void shutdown();
|
||||
// the pipeline of the description, made at the first call (from the cache on the disk, when it has it). null when it can't be made
|
||||
VkPipeline get(pipeline_desc const &Desc);
|
||||
VkPipeline compute(std::shared_ptr<shader_code const> const &Shader, VkPipelineLayout Layout, std::vector<std::uint32_t> const &Specialization = {}, std::string const &Name = {});
|
||||
std::string info() const;
|
||||
|
||||
private:
|
||||
std::uint64_t key_of(pipeline_desc const &Desc) const;
|
||||
|
||||
device const *m_device{nullptr};
|
||||
VkPipelineCache m_cache{VK_NULL_HANDLE};
|
||||
std::string m_cachepath;
|
||||
std::unordered_map<std::uint64_t, VkPipeline> m_pipelines;
|
||||
double m_creationms{0.0};
|
||||
};
|
||||
|
||||
// the dynamic state of a pipeline in its plain form: the whole of Extent, no culling, no depth, triangles
|
||||
void set_draw_state(VkCommandBuffer Commands, VkExtent2D Extent);
|
||||
|
||||
} // namespace vk14
|
||||
@@ -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()
|
||||
|
||||
@@ -14,6 +14,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#pragma once
|
||||
|
||||
#include "rendering/vulkan14/vulkan14common.h"
|
||||
#include "rendering/vulkan14/vulkan14passes.h"
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
@@ -44,6 +45,20 @@ class swapchain
|
||||
acquire_result acquire(std::uint32_t const Frame);
|
||||
// the image acquired, from undefined to cleared with Colour (linear) and ready to be shown
|
||||
void record_clear(VkCommandBuffer Commands, glm::vec4 const &Colour) const;
|
||||
// the image acquired and its view
|
||||
VkImage image() const
|
||||
{
|
||||
return m_images[m_image];
|
||||
}
|
||||
VkImageView view() const
|
||||
{
|
||||
return m_views[m_image];
|
||||
}
|
||||
// a use of the image acquired by a pass (vk14::pass_list); the image starts the frame undefined, after the acquire semaphore
|
||||
image_use use(VkImageLayout Layout, VkPipelineStageFlags2 Stage, VkAccessFlags2 Access)
|
||||
{
|
||||
return image_use{m_images[m_image], VK_IMAGE_ASPECT_COLOR_BIT, &m_states[m_image], Layout, Stage, Access};
|
||||
}
|
||||
// the semaphore signalled when the image of the frame Frame is acquired
|
||||
VkSemaphore acquire_semaphore(std::uint32_t const Frame) const
|
||||
{
|
||||
@@ -111,6 +126,8 @@ class swapchain
|
||||
bool m_rebuild{false};
|
||||
|
||||
std::vector<VkImage> m_images;
|
||||
// the states of the images for the barriers of the passes
|
||||
std::vector<image_state> m_states;
|
||||
std::vector<VkImageView> m_views;
|
||||
// one per image: a semaphore given to the presentation can be reused only once the image comes back
|
||||
std::vector<VkSemaphore> m_presentsemaphores;
|
||||
|
||||
596
rendering/vulkan14/vulkan14texture.cpp
Normal file
596
rendering/vulkan14/vulkan14texture.cpp
Normal file
@@ -0,0 +1,596 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14texture.h"
|
||||
#include "rendering/vulkan14/vulkan14device.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
// uploader
|
||||
|
||||
void uploader::init(allocator const &Allocator, deletion_queue &Deletions, VkPhysicalDevice Physical)
|
||||
{
|
||||
m_allocator = &Allocator;
|
||||
m_deletions = &Deletions;
|
||||
m_physical = Physical;
|
||||
}
|
||||
|
||||
bool uploader::can_blit(VkFormat Format) const
|
||||
{
|
||||
auto const lookup{m_blittable.find(Format)};
|
||||
if (lookup != m_blittable.end())
|
||||
{
|
||||
return lookup->second;
|
||||
}
|
||||
VkFormatProperties properties{};
|
||||
vkGetPhysicalDeviceFormatProperties(m_physical, Format, &properties);
|
||||
auto const needed{VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT};
|
||||
auto const result{(properties.optimalTilingFeatures & needed) == needed};
|
||||
m_blittable.emplace(Format, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void uploader::begin_frame(VkCommandBuffer Commands, std::uint64_t Value)
|
||||
{
|
||||
m_commands = Commands;
|
||||
m_value = Value;
|
||||
m_recorded = false;
|
||||
auto pending{std::move(m_pending)};
|
||||
m_pending.clear();
|
||||
for (auto &job : pending)
|
||||
{
|
||||
record(job);
|
||||
}
|
||||
}
|
||||
|
||||
bool uploader::end_frame()
|
||||
{
|
||||
m_commands = VK_NULL_HANDLE;
|
||||
return m_recorded;
|
||||
}
|
||||
|
||||
bool uploader::upload(std::shared_ptr<image> const &Image, void const *Data, VkDeviceSize Size, std::vector<level> const &Levels, bool const Mipmaps)
|
||||
{
|
||||
job upload;
|
||||
upload.target = Image;
|
||||
upload.levels = Levels;
|
||||
upload.mipmaps = Mipmaps;
|
||||
upload.staging = std::make_shared<buffer>();
|
||||
if ((false == upload.staging->create(*m_allocator, Size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, memory_kind::upload)) || (upload.staging->mapped() == nullptr))
|
||||
{
|
||||
log_error("no staging buffer of " + memory_size_string(Size) + " for an upload");
|
||||
return false;
|
||||
}
|
||||
std::memcpy(upload.staging->mapped(), Data, static_cast<std::size_t>(Size));
|
||||
upload.staging->flush(0, Size);
|
||||
m_uploaded += Size;
|
||||
if (m_commands != VK_NULL_HANDLE)
|
||||
{
|
||||
record(upload);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pending.emplace_back(std::move(upload));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void uploader::record(job &Job)
|
||||
{
|
||||
auto &target{*Job.target};
|
||||
auto const commands{m_commands};
|
||||
auto const aspect{target.aspect()};
|
||||
auto const layers{target.desc().layers};
|
||||
auto const mips{target.desc().mips};
|
||||
|
||||
transition(commands, target, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
|
||||
std::vector<VkBufferImageCopy2> regions;
|
||||
for (std::size_t i = 0; i < Job.levels.size(); ++i)
|
||||
{
|
||||
VkBufferImageCopy2 region{};
|
||||
region.sType = VK_STRUCTURE_TYPE_BUFFER_IMAGE_COPY_2;
|
||||
region.bufferOffset = Job.levels[i].offset;
|
||||
region.imageSubresource = {aspect, static_cast<std::uint32_t>(i), 0, layers};
|
||||
region.imageExtent = Job.levels[i].extent;
|
||||
regions.push_back(region);
|
||||
}
|
||||
VkCopyBufferToImageInfo2 copy{};
|
||||
copy.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_TO_IMAGE_INFO_2;
|
||||
copy.srcBuffer = Job.staging->handle();
|
||||
copy.dstImage = target.handle();
|
||||
copy.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
copy.regionCount = static_cast<std::uint32_t>(regions.size());
|
||||
copy.pRegions = regions.data();
|
||||
vkCmdCopyBufferToImage2(commands, ©);
|
||||
|
||||
auto const given{static_cast<std::uint32_t>(Job.levels.size())};
|
||||
if (Job.mipmaps && (mips > given) && (given > 0))
|
||||
{
|
||||
// the other levels made by blits, each from the one before it
|
||||
auto barrier = [&](std::uint32_t Level, std::uint32_t Count, VkImageLayout From, VkImageLayout To, VkPipelineStageFlags2 SrcStage, VkAccessFlags2 SrcAccess, VkPipelineStageFlags2 DstStage, VkAccessFlags2 DstAccess) {
|
||||
VkImageMemoryBarrier2 entry{};
|
||||
entry.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
|
||||
entry.srcStageMask = SrcStage;
|
||||
entry.srcAccessMask = SrcAccess;
|
||||
entry.dstStageMask = DstStage;
|
||||
entry.dstAccessMask = DstAccess;
|
||||
entry.oldLayout = From;
|
||||
entry.newLayout = To;
|
||||
entry.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
entry.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
entry.image = target.handle();
|
||||
entry.subresourceRange = {aspect, Level, Count, 0, layers};
|
||||
VkDependencyInfo dependency{};
|
||||
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
|
||||
dependency.imageMemoryBarrierCount = 1;
|
||||
dependency.pImageMemoryBarriers = &entry;
|
||||
vkCmdPipelineBarrier2(commands, &dependency);
|
||||
};
|
||||
auto const extent{target.desc().extent};
|
||||
for (std::uint32_t level = given; level < mips; ++level)
|
||||
{
|
||||
barrier(level - 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_2_COPY_BIT | VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_BLIT_BIT,
|
||||
VK_ACCESS_2_TRANSFER_READ_BIT);
|
||||
VkImageBlit2 blit{};
|
||||
blit.sType = VK_STRUCTURE_TYPE_IMAGE_BLIT_2;
|
||||
blit.srcSubresource = {aspect, level - 1, 0, layers};
|
||||
blit.srcOffsets[1] = {static_cast<std::int32_t>(std::max(extent.width >> (level - 1), 1u)), static_cast<std::int32_t>(std::max(extent.height >> (level - 1), 1u)), 1};
|
||||
blit.dstSubresource = {aspect, level, 0, layers};
|
||||
blit.dstOffsets[1] = {static_cast<std::int32_t>(std::max(extent.width >> level, 1u)), static_cast<std::int32_t>(std::max(extent.height >> level, 1u)), 1};
|
||||
VkBlitImageInfo2 info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_BLIT_IMAGE_INFO_2;
|
||||
info.srcImage = target.handle();
|
||||
info.srcImageLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||
info.dstImage = target.handle();
|
||||
info.dstImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||||
info.regionCount = 1;
|
||||
info.pRegions = &blit;
|
||||
info.filter = VK_FILTER_LINEAR;
|
||||
vkCmdBlitImage2(commands, &info);
|
||||
}
|
||||
barrier(0, mips - 1, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_NONE, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
|
||||
barrier(mips - 1, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_BLIT_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
|
||||
target.state = image_state{VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT};
|
||||
}
|
||||
else
|
||||
{
|
||||
transition(commands, target, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_SHADER_SAMPLED_READ_BIT);
|
||||
}
|
||||
m_recorded = true;
|
||||
// the staging buffer goes, and the image may, once the gpu is done with the frame
|
||||
m_deletions->push(m_value, [staging = Job.staging, image = Job.target]() {});
|
||||
}
|
||||
|
||||
// image table
|
||||
|
||||
std::uint64_t image_table::add(std::shared_ptr<image> const &Image, VkSampler Sampler)
|
||||
{
|
||||
std::uint64_t id;
|
||||
if (false == m_free.empty())
|
||||
{
|
||||
id = m_free.back();
|
||||
m_free.pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
id = m_entries.size();
|
||||
m_entries.emplace_back();
|
||||
}
|
||||
m_entries[id] = entry{Image, Sampler};
|
||||
return id;
|
||||
}
|
||||
|
||||
void image_table::replace(std::uint64_t Id, std::shared_ptr<image> const &Image, VkSampler Sampler)
|
||||
{
|
||||
if ((Id > 0) && (Id < m_entries.size()))
|
||||
{
|
||||
m_entries[Id] = entry{Image, Sampler};
|
||||
}
|
||||
}
|
||||
|
||||
void image_table::remove(std::uint64_t Id, deletion_queue &Deletions, std::uint64_t Value)
|
||||
{
|
||||
if ((Id == 0) || (Id >= m_entries.size()) || (m_entries[Id].target == nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Deletions.push(Value, [image = std::move(m_entries[Id].target)]() {});
|
||||
m_entries[Id] = entry{};
|
||||
m_free.push_back(Id);
|
||||
}
|
||||
|
||||
image_table::entry const *image_table::find(std::uint64_t Id) const
|
||||
{
|
||||
if ((Id == 0) || (Id >= m_entries.size()) || (m_entries[Id].target == nullptr))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return &m_entries[Id];
|
||||
}
|
||||
|
||||
void image_table::clear()
|
||||
{
|
||||
m_entries.assign(1, entry{});
|
||||
m_free.clear();
|
||||
}
|
||||
|
||||
// formats
|
||||
|
||||
texture_format texture_format_of(GLint Format, GLint Hint)
|
||||
{
|
||||
// (etc2 of the ktx files isn't mapped: the desktop gpus don't sample it, and the feature isn't enabled on the device; the
|
||||
// opengl driver decodes it)
|
||||
// the material wants srgb colours (the default), or values: the compressed formats keep their alpha either way, as in the
|
||||
// opengl renderer (opengl_texture::mapping)
|
||||
auto const srgb{(Hint == 0) || (Hint == GL_SRGB_ALPHA) || (Hint == GL_SRGB)};
|
||||
texture_format result;
|
||||
switch (Format)
|
||||
{
|
||||
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
|
||||
result.format = ((srgb || (Format == GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT)) ? VK_FORMAT_BC1_RGBA_SRGB_BLOCK : VK_FORMAT_BC1_RGBA_UNORM_BLOCK);
|
||||
result.compressed = true;
|
||||
result.block_bytes = 8;
|
||||
return result;
|
||||
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
|
||||
result.format = ((srgb || (Format == GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT)) ? VK_FORMAT_BC2_SRGB_BLOCK : VK_FORMAT_BC2_UNORM_BLOCK);
|
||||
result.compressed = true;
|
||||
result.block_bytes = 16;
|
||||
return result;
|
||||
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
|
||||
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
|
||||
result.format = ((srgb || (Format == GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT)) ? VK_FORMAT_BC3_SRGB_BLOCK : VK_FORMAT_BC3_UNORM_BLOCK);
|
||||
result.compressed = true;
|
||||
result.block_bytes = 16;
|
||||
return result;
|
||||
case GL_RGBA:
|
||||
case GL_BGRA:
|
||||
case GL_RGB:
|
||||
result.format = (Format == GL_BGRA ? (srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM) : (srgb ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM));
|
||||
// rgb texels aren't sampled by every gpu, they are made rgba
|
||||
result.expand_rgb = (Format == GL_RGB);
|
||||
break;
|
||||
default:
|
||||
return result;
|
||||
}
|
||||
// what the material takes of the colour, as the internal formats of opengl give it (GL_SRGB8, GL_RG8, GL_R8: the missing
|
||||
// channels read 0, the alpha 1)
|
||||
switch (Hint)
|
||||
{
|
||||
case GL_SRGB:
|
||||
case GL_RGB:
|
||||
result.components.a = VK_COMPONENT_SWIZZLE_ONE;
|
||||
break;
|
||||
case GL_RG:
|
||||
result.components.b = VK_COMPONENT_SWIZZLE_ZERO;
|
||||
result.components.a = VK_COMPONENT_SWIZZLE_ONE;
|
||||
break;
|
||||
case GL_RED:
|
||||
result.components.g = VK_COMPONENT_SWIZZLE_ZERO;
|
||||
result.components.b = VK_COMPONENT_SWIZZLE_ZERO;
|
||||
result.components.a = VK_COMPONENT_SWIZZLE_ONE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// texture
|
||||
|
||||
vulkan14_texture::~vulkan14_texture()
|
||||
{
|
||||
if (m_resources.pending != nullptr)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_resources.pending->mutex);
|
||||
auto &list{m_resources.pending->textures};
|
||||
list.erase(std::remove(list.begin(), list.end(), this), list.end());
|
||||
}
|
||||
}
|
||||
|
||||
bool vulkan14_texture::create(bool const Static)
|
||||
{
|
||||
if ((data_state == resource_state::none) && (true == reload_on_use))
|
||||
{
|
||||
// the data was dropped along with the gpu image when the texture was far away
|
||||
reload_on_use = false;
|
||||
load();
|
||||
}
|
||||
if ((data_state == resource_state::loading) && (request != nullptr))
|
||||
{
|
||||
complete_data();
|
||||
}
|
||||
if (m_made)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (data_state != resource_state::good)
|
||||
{
|
||||
// don't bother until we have useful texture data
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const format{texture_format_of(data_format, components_hint != 0 ? components_hint : GL_SRGB_ALPHA)};
|
||||
VkFormatProperties properties{};
|
||||
if (format.format != VK_FORMAT_UNDEFINED)
|
||||
{
|
||||
vkGetPhysicalDeviceFormatProperties(m_resources.gpu->physical(), format.format, &properties);
|
||||
}
|
||||
if ((format.format == VK_FORMAT_UNDEFINED) || ((properties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) == 0))
|
||||
{
|
||||
ErrorLog("Bad texture: the format of \"" + name + "\" can't be sampled by the gpu", logtype::texture);
|
||||
on_failed();
|
||||
is_ready = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uploader::level> levels;
|
||||
std::vector<std::uint8_t> expanded;
|
||||
std::uint8_t const *source{data.data()};
|
||||
VkDeviceSize size{data.size()};
|
||||
auto width{data_width}, height{data_height};
|
||||
if (format.compressed)
|
||||
{
|
||||
VkDeviceSize offset{0};
|
||||
for (unsigned int level = 0; level < data_mapcount; ++level)
|
||||
{
|
||||
auto const levelsize{static_cast<VkDeviceSize>((std::max(width, 4u) + 3) / 4) * ((std::max(height, 4u) + 3) / 4) * format.block_bytes};
|
||||
if (offset + levelsize > data.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
levels.push_back({offset, {width, height, 1}});
|
||||
offset += levelsize;
|
||||
width = std::max(width / 2, 1u);
|
||||
height = std::max(height / 2, 1u);
|
||||
}
|
||||
size = offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (format.expand_rgb)
|
||||
{
|
||||
auto const pixels{static_cast<std::size_t>(data_width) * data_height};
|
||||
expanded.resize(pixels * 4);
|
||||
for (std::size_t i = 0; i < pixels; ++i)
|
||||
{
|
||||
expanded[i * 4 + 0] = data[i * 3 + 0];
|
||||
expanded[i * 4 + 1] = data[i * 3 + 1];
|
||||
expanded[i * 4 + 2] = data[i * 3 + 2];
|
||||
expanded[i * 4 + 3] = 0xff;
|
||||
}
|
||||
source = expanded.data();
|
||||
size = expanded.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
size = static_cast<VkDeviceSize>(data_width) * data_height * 4;
|
||||
}
|
||||
levels.push_back({0, {data_width, data_height, 1}});
|
||||
}
|
||||
auto const available{expanded.empty() ? data.size() : expanded.size()};
|
||||
if (levels.empty() || (size > available) || (data_width == 0) || (data_height == 0))
|
||||
{
|
||||
ErrorLog("Bad texture: \"" + name + "\" holds less data than its size needs", logtype::texture);
|
||||
on_failed();
|
||||
is_ready = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// a single level is made into the whole chain on the gpu, as glGenerateMipmap() does for the opengl renderer
|
||||
auto const generate{(levels.size() == 1) && (false == format.compressed) && m_resources.uploads->can_blit(format.format)};
|
||||
image_desc desc;
|
||||
desc.format = format.format;
|
||||
desc.extent = {data_width, data_height, 1};
|
||||
desc.mips = (generate ? static_cast<std::uint32_t>(std::floor(std::log2(std::max(data_width, data_height)))) + 1 : static_cast<std::uint32_t>(levels.size()));
|
||||
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | (generate ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0);
|
||||
desc.components = format.components;
|
||||
auto image{std::make_shared<vk14::image>()};
|
||||
if (false == image->create(*m_resources.memory, desc, name))
|
||||
{
|
||||
ErrorLog("Bad texture: no gpu image for \"" + name + "\"", logtype::texture);
|
||||
on_failed();
|
||||
is_ready = true;
|
||||
return true;
|
||||
}
|
||||
if (false == m_resources.uploads->upload(image, source, size, levels, generate))
|
||||
{
|
||||
// never filled, so never sampled
|
||||
ErrorLog("Bad texture: \"" + name + "\" couldn't be uploaded", logtype::texture);
|
||||
on_failed();
|
||||
is_ready = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// the traits of the file: clamped along s or t
|
||||
auto const clamped{(traits.find('s') != std::string::npos) || (traits.find('t') != std::string::npos)};
|
||||
m_image = image;
|
||||
m_id = m_resources.images->add(image, clamped ? m_resources.sampling->clamp_aniso : m_resources.sampling->repeat_aniso);
|
||||
m_made = true;
|
||||
|
||||
if ((true == Global.ResourceMove) || (false == Global.ResourceSweep) || (true == streamed()))
|
||||
{
|
||||
// if garbage collection is disabled we don't expect having to upload the texture more than once
|
||||
data = std::vector<unsigned char>();
|
||||
data_state = resource_state::none;
|
||||
}
|
||||
if (type == "make:")
|
||||
{
|
||||
// for generated textures send a request to have the actual content of the texture generated
|
||||
make_request();
|
||||
}
|
||||
is_static = Static;
|
||||
is_ready = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void vulkan14_texture::release()
|
||||
{
|
||||
if (false == m_made)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (is_static)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (true == streamed())
|
||||
{
|
||||
// no copy of the data is kept, it's read from the file again when the texture is needed (prefetch(), create())
|
||||
data = std::vector<unsigned char>();
|
||||
data_state = resource_state::none;
|
||||
reload_on_use = true;
|
||||
}
|
||||
else if (data_state != resource_state::good)
|
||||
{
|
||||
if (type == "make:")
|
||||
{
|
||||
// auto generated textures only store a stub
|
||||
make_stub();
|
||||
}
|
||||
else if (type != "internalsrc:")
|
||||
{
|
||||
// the data went after the upload (gfx.resource.move): read from the file again, instead of from the gpu
|
||||
reload_on_use = true;
|
||||
}
|
||||
}
|
||||
m_resources.images->remove(m_id, *m_resources.deletions, m_resources.uploads->current_value());
|
||||
m_image.reset();
|
||||
m_id = 0;
|
||||
m_made = false;
|
||||
is_ready = false;
|
||||
}
|
||||
|
||||
void vulkan14_texture::destroy()
|
||||
{
|
||||
if (m_id != 0)
|
||||
{
|
||||
m_resources.images->remove(m_id, *m_resources.deletions, m_resources.uploads->current_value());
|
||||
}
|
||||
m_image.reset();
|
||||
m_id = 0;
|
||||
m_made = false;
|
||||
}
|
||||
|
||||
void vulkan14_texture::on_failed()
|
||||
{
|
||||
// the texture counts as made, without an image (the id 0), as with the opengl renderer
|
||||
if (m_id != 0)
|
||||
{
|
||||
m_resources.images->remove(m_id, *m_resources.deletions, m_resources.uploads->current_value());
|
||||
}
|
||||
m_image.reset();
|
||||
m_id = 0;
|
||||
m_made = true;
|
||||
}
|
||||
|
||||
void vulkan14_texture::update_from_memory(size_t width, size_t height, const uint8_t *raw)
|
||||
{
|
||||
if (std::this_thread::get_id() != m_resources.thread)
|
||||
{
|
||||
// the renderer takes it over in its frame (apply_pending())
|
||||
std::lock_guard<std::mutex> lock(m_pendingmutex);
|
||||
m_pending.assign(raw, raw + width * height * 4);
|
||||
m_pendingwidth = width;
|
||||
m_pendingheight = height;
|
||||
if (m_resources.pending != nullptr)
|
||||
{
|
||||
std::lock_guard<std::mutex> listlock(m_resources.pending->mutex);
|
||||
auto &list{m_resources.pending->textures};
|
||||
if (std::find(list.begin(), list.end(), this) == list.end())
|
||||
{
|
||||
list.push_back(this);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
upload_rgba(raw, width, height);
|
||||
}
|
||||
|
||||
void vulkan14_texture::apply_pending()
|
||||
{
|
||||
std::vector<std::uint8_t> pending;
|
||||
std::size_t width{0}, height{0};
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_pendingmutex);
|
||||
pending.swap(m_pending);
|
||||
width = m_pendingwidth;
|
||||
height = m_pendingheight;
|
||||
}
|
||||
if (false == pending.empty())
|
||||
{
|
||||
upload_rgba(pending.data(), width, height);
|
||||
}
|
||||
}
|
||||
|
||||
void vulkan14_texture::upload_rgba(std::uint8_t const *Data, std::size_t Width, std::size_t Height)
|
||||
{
|
||||
// as the opengl renderer: srgb rgba with mipmaps, the image made anew when the size changes
|
||||
auto const width{static_cast<std::uint32_t>(Width)}, height{static_cast<std::uint32_t>(Height)};
|
||||
auto const format{VK_FORMAT_R8G8B8A8_SRGB};
|
||||
auto const generate{m_resources.uploads->can_blit(format)};
|
||||
auto target{m_image};
|
||||
auto const fresh{(m_image == nullptr) || (m_image->desc().extent.width != width) || (m_image->desc().extent.height != height) || (m_image->desc().format != format)};
|
||||
if (fresh)
|
||||
{
|
||||
image_desc desc;
|
||||
desc.format = format;
|
||||
desc.extent = {width, height, 1};
|
||||
desc.mips = (generate ? static_cast<std::uint32_t>(std::floor(std::log2(std::max(width, height)))) + 1 : 1);
|
||||
desc.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | (generate ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0);
|
||||
target = std::make_shared<vk14::image>();
|
||||
if (false == target->create(*m_resources.memory, desc, name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
// a new image goes to the table only once it has its content
|
||||
if (false == m_resources.uploads->upload(target, Data, static_cast<VkDeviceSize>(width) * height * 4, {{0, {width, height, 1}}}, generate))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (fresh)
|
||||
{
|
||||
auto const sampler{m_resources.sampling->repeat_aniso};
|
||||
if (m_id != 0)
|
||||
{
|
||||
// the old image goes once the frames which drew it are done
|
||||
m_resources.deletions->push(m_resources.uploads->current_value(), [old = m_image]() {});
|
||||
m_resources.images->replace(m_id, target, sampler);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_id = m_resources.images->add(target, sampler);
|
||||
}
|
||||
m_image = target;
|
||||
}
|
||||
data_width = width;
|
||||
data_height = height;
|
||||
data_format = GL_SRGB8_ALPHA8;
|
||||
data_components = GL_RGBA;
|
||||
m_made = true;
|
||||
// the content given straight to the gpu can't be read again: released by the garbage sweep, the texture would come back as
|
||||
// the grey stub
|
||||
if (type == "internalsrc:")
|
||||
{
|
||||
is_static = true;
|
||||
is_ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace vk14
|
||||
187
rendering/vulkan14/vulkan14texture.h
Normal file
187
rendering/vulkan14/vulkan14texture.h
Normal file
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// the images the vulkan renderer samples: the textures of the files (texture_source, decoded as for the opengl renderer and
|
||||
// laid out the same way in the memory, decision K8 of the plan), the images of the user interface, and their uploads.
|
||||
//
|
||||
// the uploads are copies through staging buffers recorded into the command buffer of uploads of the frame being made, which goes
|
||||
// to the queue before the commands of the frame in the same submit; an upload asked for between the frames waits for the next
|
||||
// one. every image has an id in one table (decision K10): ITexture::get_id() of the textures, the ids of the images of the user
|
||||
// interface and the font of imgui are all ImTextureID, so the user interface draws any of them
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rendering/vulkan14/vulkan14memory.h"
|
||||
#include "rendering/vulkan14/vulkan14descriptors.h"
|
||||
#include "model/Texture.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace vk14
|
||||
{
|
||||
|
||||
class device;
|
||||
|
||||
// the uploads of data to the images
|
||||
class uploader
|
||||
{
|
||||
public:
|
||||
struct level
|
||||
{
|
||||
VkDeviceSize offset{0}; // in the data
|
||||
VkExtent3D extent{1, 1, 1};
|
||||
};
|
||||
|
||||
void init(allocator const &Allocator, deletion_queue &Deletions, VkPhysicalDevice Physical);
|
||||
// the commands of the uploads go to Commands until end_frame(); Value: the value of the timeline the gpu reaches when it's done
|
||||
// with the frame. the uploads waiting from between the frames are recorded first
|
||||
void begin_frame(VkCommandBuffer Commands, std::uint64_t Value);
|
||||
// whether anything was recorded to the commands of the frame
|
||||
bool end_frame();
|
||||
// the last value of the timeline the gpu may use the objects with: the frame being made, or the last one sent
|
||||
std::uint64_t current_value() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
// copies Data into Image (the levels given, the others made by blits of the first one when Mipmaps), the image left ready to be
|
||||
// sampled. Data is copied away. false: no staging memory, nothing will be copied
|
||||
bool upload(std::shared_ptr<image> const &Image, void const *Data, VkDeviceSize Size, std::vector<level> const &Levels, bool const Mipmaps);
|
||||
// the bytes uploaded since the start, for the debug panel
|
||||
std::uint64_t uploaded() const
|
||||
{
|
||||
return m_uploaded;
|
||||
}
|
||||
// whether the format can be filtered by blits (the mipmaps made on the gpu)
|
||||
bool can_blit(VkFormat Format) const;
|
||||
|
||||
private:
|
||||
struct job
|
||||
{
|
||||
std::shared_ptr<image> target;
|
||||
std::shared_ptr<buffer> staging;
|
||||
std::vector<level> levels;
|
||||
bool mipmaps{false};
|
||||
};
|
||||
void record(job &Job);
|
||||
|
||||
allocator const *m_allocator{nullptr};
|
||||
deletion_queue *m_deletions{nullptr};
|
||||
VkPhysicalDevice m_physical{VK_NULL_HANDLE};
|
||||
VkCommandBuffer m_commands{VK_NULL_HANDLE};
|
||||
std::uint64_t m_value{0};
|
||||
bool m_recorded{false};
|
||||
std::vector<job> m_pending;
|
||||
std::uint64_t m_uploaded{0};
|
||||
mutable std::unordered_map<VkFormat, bool> m_blittable;
|
||||
};
|
||||
|
||||
// the images the shaders can be given by an id
|
||||
class image_table
|
||||
{
|
||||
public:
|
||||
struct entry
|
||||
{
|
||||
std::shared_ptr<image> target;
|
||||
VkSampler sampler{VK_NULL_HANDLE};
|
||||
};
|
||||
// a new id for the image (0 is no image)
|
||||
std::uint64_t add(std::shared_ptr<image> const &Image, VkSampler Sampler);
|
||||
void replace(std::uint64_t Id, std::shared_ptr<image> const &Image, VkSampler Sampler);
|
||||
// the id is free again, the image goes once the gpu is done with it
|
||||
void remove(std::uint64_t Id, deletion_queue &Deletions, std::uint64_t Value);
|
||||
entry const *find(std::uint64_t Id) const;
|
||||
void clear();
|
||||
std::size_t size() const
|
||||
{
|
||||
return m_entries.size() - m_free.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<entry> m_entries{entry{}};
|
||||
std::vector<std::uint64_t> m_free;
|
||||
};
|
||||
|
||||
struct vulkan14_texture;
|
||||
|
||||
// the textures given content by other threads, waiting for the thread of the renderer
|
||||
struct pending_textures
|
||||
{
|
||||
std::mutex mutex;
|
||||
std::vector<vulkan14_texture *> textures;
|
||||
};
|
||||
|
||||
// what the images of the renderer need from it
|
||||
struct resources
|
||||
{
|
||||
device const *gpu{nullptr};
|
||||
allocator *memory{nullptr};
|
||||
uploader *uploads{nullptr};
|
||||
deletion_queue *deletions{nullptr};
|
||||
samplers const *sampling{nullptr};
|
||||
image_table *images{nullptr};
|
||||
// the thread of the renderer: the content given by other threads waits for it (update_from_memory())
|
||||
std::thread::id thread;
|
||||
pending_textures *pending{nullptr};
|
||||
};
|
||||
|
||||
// the vulkan format of the data of a texture and how the data has to be changed for it
|
||||
struct texture_format
|
||||
{
|
||||
VkFormat format{VK_FORMAT_UNDEFINED};
|
||||
bool expand_rgb{false}; // rgb texels made rgba
|
||||
bool compressed{false};
|
||||
int block_bytes{0}; // of a block of 4x4 texels, for the compressed formats
|
||||
// the channels the material reads, as the internal format of opengl would leave them
|
||||
VkComponentMapping components{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY};
|
||||
};
|
||||
// Format: data_format of a texture_source, Hint: the components the material wants (GL_SRGB_ALPHA ...)
|
||||
texture_format texture_format_of(GLint Format, GLint Hint);
|
||||
|
||||
// a texture of the vulkan renderer
|
||||
struct vulkan14_texture : public texture_source
|
||||
{
|
||||
explicit vulkan14_texture(resources const &Resources) : m_resources(Resources) {}
|
||||
~vulkan14_texture() override;
|
||||
|
||||
bool create(bool const Static = false) override;
|
||||
void release() override;
|
||||
size_t get_id() const override
|
||||
{
|
||||
return static_cast<size_t>(m_id);
|
||||
}
|
||||
void update_from_memory(size_t width, size_t height, const uint8_t *data) override;
|
||||
bool resident() const override
|
||||
{
|
||||
return m_made;
|
||||
}
|
||||
void destroy() override;
|
||||
|
||||
// the content given by update_from_memory() from another thread, uploaded by the thread of the renderer
|
||||
void apply_pending();
|
||||
|
||||
protected:
|
||||
void on_failed() override;
|
||||
|
||||
private:
|
||||
void upload_rgba(std::uint8_t const *Data, std::size_t Width, std::size_t Height);
|
||||
|
||||
resources const &m_resources;
|
||||
std::shared_ptr<image> m_image;
|
||||
std::uint64_t m_id{0};
|
||||
bool m_made{false};
|
||||
// content from another thread, waiting for the thread of the renderer
|
||||
std::mutex m_pendingmutex;
|
||||
std::vector<std::uint8_t> m_pending;
|
||||
std::size_t m_pendingwidth{0};
|
||||
std::size_t m_pendingheight{0};
|
||||
};
|
||||
|
||||
} // namespace vk14
|
||||
19
rendering/vulkan14/vulkan14vma.cpp
Normal file
19
rendering/vulkan14/vulkan14vma.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// the implementation of the vulkan memory allocator (ref/VulkanMemoryAllocator). the functions of vulkan are given to it by
|
||||
// vk14::allocator, as volk loaded them (vmaImportVulkanFunctionsFromVolk)
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "rendering/vulkan14/vulkan14common.h"
|
||||
|
||||
#define VMA_STATIC_VULKAN_FUNCTIONS 0
|
||||
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0
|
||||
#define VMA_IMPLEMENTATION
|
||||
#include <vk_mem_alloc.h>
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "rendering/renderer.h"
|
||||
#include "gl/shader.h"
|
||||
#include "gl/ubo.h"
|
||||
#include "scripting/PyInt.h"
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
|
||||
43
shaders/README.md
Normal file
43
shaders/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Shaders
|
||||
|
||||
| Directory | What is in it |
|
||||
|---|---|
|
||||
| `ogl46/` | GLSL sources of the OpenGL 4.6 renderer |
|
||||
| `ogl46bin/` | SPIR-V binaries of `ogl46/` (optional, made by `compile_spirv.py`) |
|
||||
| `vk14/` | GLSL sources of the Vulkan 1.4 renderer |
|
||||
| `vk14bin/` | SPIR-V binaries of `vk14/` (optional, made by `compile_spirv.py`) |
|
||||
|
||||
The renderers load the binary of a shader first, when there is one and it was compiled from the sources that are there now
|
||||
(`manifest.txt` of the directory of the binaries lists the sources of each binary with their hashes). Otherwise the source is
|
||||
compiled: by the OpenGL driver for `ogl46/`, by glslang inside the simulator for `vk14/` (with a cache of the results in the
|
||||
directory of the user). `gfx.shaders.binaries no` in `eu07.ini` makes them always use the sources. The sources are needed
|
||||
even with the binaries: the directives of the materials (`#pragma texture`, `#pragma param`) are read from them.
|
||||
|
||||
## Compiling to SPIR-V
|
||||
|
||||
python shaders/compile_spirv.py
|
||||
|
||||
compiles every shader of `ogl46/` (with the variant `MODEL_DRAWS` of the vertex and fragment shaders) and of `vk14/` with
|
||||
`glslc` (Vulkan SDK, `VULKAN_SDK`, or `--glslc <path>`). A single file compiles the same way by hand:
|
||||
|
||||
glslc --target-env=opengl4.5 ogl46/mat_default.frag -o ogl46bin/mat_default.frag.spv
|
||||
glslc --target-env=opengl4.5 -DMODEL_DRAWS=1 ogl46/vertex.vert -o ogl46bin/vertex.vert.MODEL_DRAWS.spv
|
||||
glslc --target-env=vulkan1.4 vk14/imgui.frag -o vk14bin/imgui.frag.spv
|
||||
|
||||
A binary made by hand isn't in the manifest, so it is taken without being compared with its source. After a change of a
|
||||
source, compile the binaries again (or delete them): a binary older than its source is not used, and the log says so.
|
||||
|
||||
## Writing shaders
|
||||
|
||||
* Every stage file starts with `#version 460 core` and `#define STAGE_VERTEX 1` (`STAGE_FRAGMENT`, `STAGE_GEOMETRY`,
|
||||
`STAGE_COMPUTE`); files included by others (`.glsl`) don't.
|
||||
* Includes: `#include "file.glsl"`, relative to the directory of the file.
|
||||
* Every input and output between the stages has an explicit `layout(location = N)`, every sampler, image and block an explicit
|
||||
binding, every loose uniform an explicit location: SPIR-V has no names to link by. The locations of the inputs and outputs
|
||||
of `ogl46/` are one table for all the shaders (a name has the same location everywhere), see `ogl46/common.glsl`.
|
||||
* The settings of the game (`SHADOWMAP_ENABLED`, `ENVMAP_ENABLED`, `MOTIONBLUR_ENABLED`, `POSTFX_ENABLED`,
|
||||
`EXTRAEFFECTS_ENABLED`, `OUTPUT_GAMMA`) are `const bool` (specialization constants in the binaries, `ogl46/settings.glsl`):
|
||||
use them in `if`, not in `#if`.
|
||||
* The constants of the game (bindings, sizes) are written in `ogl46/common.glsl` and `vk14/common.glsl` for the binaries; the
|
||||
game puts its own values before the sources and checks at the start that the files have the same ones (the shader
|
||||
validator in `ci_shadervalidator` checks them too).
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
in vec2 f_coord;
|
||||
|
||||
#texture (tex1, 0, sRGB_A)
|
||||
uniform sampler2D tex1;
|
||||
|
||||
#include <common>
|
||||
#include <tonemapping.glsl>
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
#if MOTIONBLUR_ENABLED
|
||||
layout(location = 1) out vec4 out_motion;
|
||||
#endif
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 tex_color = texture(tex1, f_coord);
|
||||
#if POSTFX_ENABLED
|
||||
out_color = tex_color * param[0];
|
||||
#else
|
||||
out_color = tonemap(tex_color * param[0]);
|
||||
#endif
|
||||
#if MOTIONBLUR_ENABLED
|
||||
out_motion = vec4(0.0f);
|
||||
#endif
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
in vec3 f_color;
|
||||
in vec4 f_pos;
|
||||
|
||||
#include <common>
|
||||
#include <apply_fog.glsl>
|
||||
#include <tonemapping.glsl>
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
#if MOTIONBLUR_ENABLED
|
||||
layout(location = 1) out vec4 out_motion;
|
||||
#endif
|
||||
|
||||
#include <sky_shoulder.glsl>
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 col = sky_shoulder(pow(max(f_color.rgb, vec3(0.0)), vec3(2.2)));
|
||||
#if POSTFX_ENABLED
|
||||
out_color = vec4(apply_fog(col), 1.0f);
|
||||
#else
|
||||
out_color = tonemap(vec4(apply_fog(col), 1.0f));
|
||||
#endif
|
||||
#if MOTIONBLUR_ENABLED
|
||||
out_motion = vec4(0.0f);
|
||||
#endif
|
||||
}
|
||||
130
shaders/compile_spirv.py
Normal file
130
shaders/compile_spirv.py
Normal file
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env python3
|
||||
# compiles the shaders of shaders/ogl46 and shaders/vk14 to SPIR-V with glslc, into shaders/ogl46bin and shaders/vk14bin,
|
||||
# which the renderers load before they compile the sources (gfx.shaders.binaries). each directory of binaries gets
|
||||
# manifest.txt: the source files each binary was compiled from, with their hashes, so the game doesn't take a binary older
|
||||
# than its sources.
|
||||
#
|
||||
# python shaders/compile_spirv.py [--glslc <path>] [--only ogl46|vk14] [<shaders directory>]
|
||||
#
|
||||
# glslc comes with the Vulkan SDK (VULKAN_SDK is looked at) or with the shaderc package. the shaders of opengl are compiled
|
||||
# for --target-env=opengl4.5 (SPIR-V 1.0, which opengl 4.6 takes), with the variant of the multi draws (MODEL_DRAWS) of the
|
||||
# vertex and fragment shaders; those of vulkan for --target-env=vulkan1.4. a single file can be compiled by hand the same way:
|
||||
# glslc --target-env=opengl4.5 shaders/ogl46/mat_default.frag -o shaders/ogl46bin/mat_default.frag.spv
|
||||
# glslc --target-env=opengl4.5 -DMODEL_DRAWS=1 shaders/ogl46/vertex.vert -o shaders/ogl46bin/vertex.vert.MODEL_DRAWS.spv
|
||||
# glslc --target-env=vulkan1.4 shaders/vk14/imgui.frag -o shaders/vk14bin/imgui.frag.spv
|
||||
# (a binary compiled by hand isn't in the manifest, so the game takes it without comparing it with its sources)
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
STAGES = ('.vert', '.frag', '.geom', '.comp')
|
||||
TREES = {
|
||||
# source directory, binary directory, target environment, variants of the vertex and fragment shaders
|
||||
'ogl46': ('ogl46', 'ogl46bin', 'opengl4.5', ['MODEL_DRAWS']),
|
||||
'vk14': ('vk14', 'vk14bin', 'vulkan1.4', []),
|
||||
}
|
||||
INCLUDE = re.compile(r'^[ \t]*#include[ \t]*[<"]([^>"]+)[>"]', re.M)
|
||||
|
||||
|
||||
def file_hash(path):
|
||||
# fnv-1a 64 of the text with the line ends made \n, the same as gfx::shader_file_hash()
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read().replace(b'\r\n', b'\n')
|
||||
h = 14695981039346656037
|
||||
for b in data:
|
||||
h ^= b
|
||||
h = (h * 1099511628211) & 0xFFFFFFFFFFFFFFFF
|
||||
return '%016x' % h
|
||||
|
||||
|
||||
def dependencies(directory, name, found=None):
|
||||
# the file and the files it includes, the same way as gfx::preprocess_shader()
|
||||
if found is None:
|
||||
found = []
|
||||
if name in found:
|
||||
return found
|
||||
found.append(name)
|
||||
with open(os.path.join(directory, name), 'r', encoding='utf-8', errors='replace') as f:
|
||||
text = f.read()
|
||||
for include in INCLUDE.findall(text):
|
||||
if include == 'common':
|
||||
include = 'common.glsl'
|
||||
dependencies(directory, include, found)
|
||||
return found
|
||||
|
||||
|
||||
def find_glslc(explicit):
|
||||
if explicit:
|
||||
return explicit
|
||||
sdk = os.environ.get('VULKAN_SDK')
|
||||
if sdk:
|
||||
for sub in ('Bin', 'bin'):
|
||||
for exe in ('glslc.exe', 'glslc'):
|
||||
path = os.path.join(sdk, sub, exe)
|
||||
if os.path.isfile(path):
|
||||
return path
|
||||
return shutil.which('glslc')
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='compiles the shaders of MaSzyna to SPIR-V with glslc')
|
||||
parser.add_argument('shaders', nargs='?', default=os.path.dirname(os.path.abspath(__file__)), help='the shaders directory (the one of this script by default)')
|
||||
parser.add_argument('--glslc', help='path of glslc')
|
||||
parser.add_argument('--only', choices=sorted(TREES), help='only the shaders of one renderer')
|
||||
args = parser.parse_args()
|
||||
|
||||
glslc = find_glslc(args.glslc)
|
||||
if not glslc:
|
||||
print('glslc not found: install the Vulkan SDK (VULKAN_SDK) or shaderc, or give --glslc', file=sys.stderr)
|
||||
return 1
|
||||
|
||||
failed = []
|
||||
count = 0
|
||||
for key, (sourcedir, binarydir, env, variants) in TREES.items():
|
||||
if args.only and args.only != key:
|
||||
continue
|
||||
sources = os.path.join(args.shaders, sourcedir)
|
||||
binaries = os.path.join(args.shaders, binarydir)
|
||||
if not os.path.isdir(sources):
|
||||
continue
|
||||
os.makedirs(binaries, exist_ok=True)
|
||||
manifest = ['# made by compile_spirv.py: binary, source file it was compiled from, fnv-1a 64 of the source (line ends as \\n)']
|
||||
names = sorted(n for n in os.listdir(sources) if os.path.splitext(n)[1] in STAGES)
|
||||
for name in names:
|
||||
jobs = [('', [])]
|
||||
if os.path.splitext(name)[1] in ('.vert', '.frag'):
|
||||
jobs += [(v, ['-D%s=1' % v]) for v in variants]
|
||||
try:
|
||||
deps = dependencies(sources, name)
|
||||
except OSError as e:
|
||||
failed.append('%s/%s (%s)' % (sourcedir, name, e))
|
||||
continue
|
||||
for variant, defines in jobs:
|
||||
binary = name + ('.' + variant if variant else '') + '.spv'
|
||||
command = [glslc, '--target-env=' + env] + defines + [os.path.join(sources, name), '-o', os.path.join(binaries, binary)]
|
||||
result = subprocess.run(command, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
failed.append('%s/%s' % (binarydir, binary))
|
||||
sys.stderr.write(result.stderr)
|
||||
continue
|
||||
if result.stderr.strip():
|
||||
sys.stderr.write(result.stderr)
|
||||
count += 1
|
||||
for dep in deps:
|
||||
manifest.append('%s %s %s' % (binary, dep, file_hash(os.path.join(sources, dep))))
|
||||
with open(os.path.join(binaries, 'manifest.txt'), 'w', newline='\n') as f:
|
||||
f.write('\n'.join(manifest) + '\n')
|
||||
|
||||
print('%d binaries compiled' % count)
|
||||
if failed:
|
||||
print('failed: ' + ' '.join(failed), file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
#include <mat_colored.frag>
|
||||
@@ -1 +0,0 @@
|
||||
#include <mat_default.frag>
|
||||
@@ -1 +0,0 @@
|
||||
#include <mat_reflmap.frag>
|
||||
@@ -1,142 +0,0 @@
|
||||
in vec3 f_normal;
|
||||
in vec2 f_coord;
|
||||
in vec4 f_pos;
|
||||
in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos;
|
||||
in vec4 f_clip_pos;
|
||||
in vec4 f_clip_future_pos;
|
||||
in vec3 TangentFragPos;
|
||||
|
||||
#include <common>
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
#if MOTIONBLUR_ENABLED
|
||||
layout(location = 1) out vec4 out_motion;
|
||||
#endif
|
||||
|
||||
|
||||
#param (color, 0, 0, 4, diffuse)
|
||||
#param (diffuse, 1, 0, 1, diffuse)
|
||||
#param (specular, 1, 1, 1, specular)
|
||||
#param (reflection, 1, 2, 1, one)
|
||||
#param (glossiness, 1, 3, 1, glossiness)
|
||||
#param (detail_scale, 2, 0, 1, one)
|
||||
#param (detail_height_scale, 2, 1, 1, one)
|
||||
#param (height_scale, 2, 2, 1, zero)
|
||||
#param (height_offset, 2, 3, 1, zero)
|
||||
|
||||
#texture (diffuse, 0, sRGB_A)
|
||||
uniform sampler2D diffuse;
|
||||
|
||||
#texture (normalmap, 1, RGBA)
|
||||
uniform sampler2D normalmap;
|
||||
|
||||
#texture (detailnormalmap, 2, RGBA)
|
||||
uniform sampler2D detailnormalmap;
|
||||
|
||||
#define PARALLAX
|
||||
// param[2].w is the height offset, not the weathering
|
||||
#define NO_WEATHERING
|
||||
#include <light_common.glsl>
|
||||
#include <apply_fog.glsl>
|
||||
#include <tonemapping.glsl>
|
||||
|
||||
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir);
|
||||
|
||||
void main()
|
||||
{
|
||||
//parallax mapping
|
||||
vec3 viewDir = normalize(-TangentFragPos); //tangent view pos - tangent frag pos
|
||||
vec2 f_coord_p = ParallaxMapping(f_coord, viewDir);
|
||||
vec4 normal_map = texture(normalmap, f_coord_p);
|
||||
vec4 detailnormal_map = texture(detailnormalmap, f_coord_p * param[2].x);
|
||||
vec4 tex_color = texture(diffuse, f_coord_p);
|
||||
|
||||
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
|
||||
if(alphatestfail)
|
||||
discard;
|
||||
// if (tex_color.a < opacity)
|
||||
// discard;
|
||||
|
||||
vec3 fragcolor = ambient;
|
||||
|
||||
vec3 normal;
|
||||
vec3 normaldetail;
|
||||
|
||||
normaldetail.xy = detailnormal_map.rg* 2.0 - 1.0;
|
||||
normaldetail.z = sqrt(1.0 - clamp((dot(normaldetail.xy, normaldetail.xy)), 0.0, 1.0));
|
||||
normaldetail.xyz = normaldetail.xyz * param[2].y;
|
||||
normal.xy = normal_map.rg* 2.0 - 1.0;
|
||||
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
|
||||
|
||||
vec3 fragnormal = normalize(f_tbn * normalize(vec3(normal.xy + normaldetail.xy, normal.z)));
|
||||
float reflectivity = param[1].z * normal_map.a;
|
||||
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
|
||||
legacy_gloss();
|
||||
|
||||
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
|
||||
|
||||
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
|
||||
#if POSTFX_ENABLED
|
||||
out_color = color;
|
||||
#else
|
||||
out_color = tonemap(color);
|
||||
#endif
|
||||
#if MOTIONBLUR_ENABLED
|
||||
{
|
||||
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
|
||||
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
|
||||
|
||||
out_motion = vec4(a - b, 0.0f, 0.0f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir)
|
||||
{
|
||||
float pos_len = length(f_pos.xyz);
|
||||
// gradients of the coordinates taken here: inside the loop the fragments of a quad don't go the same way
|
||||
vec2 dcoorddx = dFdx(f_coord);
|
||||
vec2 dcoorddy = dFdy(f_coord);
|
||||
|
||||
if (pos_len > 100.0) {
|
||||
return f_coord;
|
||||
}
|
||||
// the shift fades out before the distance it stops at, so it doesn't switch off at once there
|
||||
float fade = 1.0 - smoothstep(80.0, 100.0, pos_len);
|
||||
|
||||
#if EXTRAEFFECTS_ENABLED
|
||||
const float minLayers = 8.0;
|
||||
const float maxLayers = 32.0;
|
||||
float LayersWeight = pos_len / 20.0;
|
||||
vec2 currentTexCoords = f_coord;
|
||||
float currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b;
|
||||
LayersWeight = min(abs(dot(vec3(0.0, 0.0, 1.0), viewDir)),LayersWeight);
|
||||
float numLayers = mix(maxLayers, minLayers, clamp(LayersWeight, 0.0, 1.0)); // number of depth layers
|
||||
float layerDepth = 1.0 / numLayers; // calculate the size of each layer
|
||||
float currentLayerDepth = 0.0; // depth of current layer
|
||||
vec2 P = viewDir.xy * param[2].z * fade; // the amount to shift the texture coordinates per layer (from vector P)
|
||||
vec2 deltaTexCoords = P / numLayers;
|
||||
|
||||
|
||||
while(currentLayerDepth < currentDepthMapValue)
|
||||
{
|
||||
currentTexCoords -= deltaTexCoords; // shift texture coordinates along direction of P
|
||||
currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b; // get depthmap value at current texture coordinates
|
||||
currentLayerDepth += layerDepth; // get depth of next layer
|
||||
}
|
||||
|
||||
vec2 prevTexCoords = currentTexCoords + deltaTexCoords; // get texture coordinates before collision (reverse operations)
|
||||
|
||||
float afterDepth = currentDepthMapValue - currentLayerDepth; // get depth after and before collision for linear interpolation
|
||||
float beforeDepth = textureGrad(normalmap, prevTexCoords, dcoorddx, dcoorddy).b - currentLayerDepth + layerDepth;
|
||||
|
||||
float weight = afterDepth / (afterDepth - beforeDepth); // interpolation of texture coordinates
|
||||
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
|
||||
|
||||
return finalTexCoords;
|
||||
#else
|
||||
float height = texture(normalmap, f_coord).b;
|
||||
vec2 p = viewDir.xy / viewDir.z * (height * (param[2].z - param[2].w) * 0.2) * fade;
|
||||
return f_coord - p;
|
||||
#endif
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
in vec3 f_normal;
|
||||
in vec2 f_coord;
|
||||
in vec4 f_pos;
|
||||
in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos;
|
||||
in vec4 f_clip_pos;
|
||||
in vec4 f_clip_future_pos;
|
||||
in vec3 TangentFragPos;
|
||||
|
||||
#include <common>
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
#if MOTIONBLUR_ENABLED
|
||||
layout(location = 1) out vec4 out_motion;
|
||||
#endif
|
||||
|
||||
|
||||
#param (color, 0, 0, 4, diffuse)
|
||||
#param (diffuse, 1, 0, 1, diffuse)
|
||||
#param (specular, 1, 1, 1, specular)
|
||||
#param (reflection, 1, 2, 1, one)
|
||||
#param (glossiness, 1, 3, 1, glossiness)
|
||||
#param (detail_scale, 2, 0, 1, one)
|
||||
#param (detail_height_scale, 2, 1, 1, one)
|
||||
#param (height_scale, 2, 2, 1, zero)
|
||||
#param (height_offset, 2, 3, 1, zero)
|
||||
|
||||
#texture (diffuse, 0, sRGB_A)
|
||||
uniform sampler2D diffuse;
|
||||
|
||||
#texture (normalmap, 1, RGBA)
|
||||
uniform sampler2D normalmap;
|
||||
|
||||
#texture (specgloss, 2, RGBA)
|
||||
uniform sampler2D specgloss;
|
||||
|
||||
#texture (detailnormalmap, 3, RGBA)
|
||||
uniform sampler2D detailnormalmap;
|
||||
|
||||
|
||||
#define PARALLAX
|
||||
// param[2].w is the height offset, not the weathering
|
||||
#define NO_WEATHERING
|
||||
#include <light_common.glsl>
|
||||
#include <apply_fog.glsl>
|
||||
#include <tonemapping.glsl>
|
||||
|
||||
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir);
|
||||
|
||||
void main()
|
||||
{
|
||||
//parallax mapping
|
||||
vec3 viewDir = normalize(vec3(0.0f, 0.0f, 0.0f) - TangentFragPos); //tangent view pos - tangent frag pos
|
||||
vec2 f_coord_p = ParallaxMapping(f_coord, viewDir);
|
||||
|
||||
vec4 normal_map = texture(normalmap, f_coord_p);
|
||||
vec4 detailnormal_map = texture(detailnormalmap, f_coord_p * param[2].x);
|
||||
vec4 tex_color = texture(diffuse, f_coord_p);
|
||||
vec4 specgloss_map = texture(specgloss, f_coord_p);
|
||||
|
||||
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
|
||||
if(alphatestfail)
|
||||
discard;
|
||||
|
||||
vec3 fragcolor = ambient;
|
||||
|
||||
vec3 normal;
|
||||
vec3 normaldetail;
|
||||
|
||||
normaldetail.xy = detailnormal_map.rg* 2.0 - 1.0;
|
||||
normaldetail.z = sqrt(1.0 - clamp((dot(normaldetail.xy, normaldetail.xy)), 0.0, 1.0));
|
||||
normaldetail.xyz = normaldetail.xyz * param[2].y;
|
||||
normal.xy = normal_map.rg* 2.0 - 1.0;
|
||||
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
|
||||
|
||||
vec3 fragnormal = normalize(f_tbn * normalize(vec3(normal.xy + normaldetail.xy, normal.z)));
|
||||
float reflectivity = param[1].z * normal_map.a;
|
||||
float specularity = specgloss_map.r;
|
||||
glossiness = specgloss_map.g * abs(param[1].w);
|
||||
metalic = specgloss_map.b;
|
||||
|
||||
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
|
||||
|
||||
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
|
||||
#if POSTFX_ENABLED
|
||||
out_color = color;
|
||||
#else
|
||||
out_color = tonemap(color);
|
||||
#endif
|
||||
#if MOTIONBLUR_ENABLED
|
||||
{
|
||||
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
|
||||
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
|
||||
|
||||
out_motion = vec4(a - b, 0.0f, 0.0f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir)
|
||||
{
|
||||
|
||||
float pos_len = length(f_pos.xyz);
|
||||
// gradients of the coordinates taken here: inside the loop the fragments of a quad don't go the same way
|
||||
vec2 dcoorddx = dFdx(f_coord);
|
||||
vec2 dcoorddy = dFdy(f_coord);
|
||||
|
||||
if (pos_len > 100.0) {
|
||||
return f_coord;
|
||||
}
|
||||
// the shift fades out before the distance it stops at, so it doesn't switch off at once there
|
||||
float fade = 1.0 - smoothstep(80.0, 100.0, pos_len);
|
||||
|
||||
#if EXTRAEFFECTS_ENABLED
|
||||
const float minLayers = 8.0;
|
||||
const float maxLayers = 32.0;
|
||||
float LayersWeight = pos_len / 20.0;
|
||||
vec2 currentTexCoords = f_coord;
|
||||
float currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b;
|
||||
LayersWeight = min(abs(dot(vec3(0.0, 0.0, 1.0), viewDir)),LayersWeight);
|
||||
float numLayers = mix(maxLayers, minLayers, clamp(LayersWeight, 0.0, 1.0)); // number of depth layers
|
||||
float layerDepth = 1.0 / numLayers; // calculate the size of each layer
|
||||
float currentLayerDepth = 0.0; // depth of current layer
|
||||
vec2 P = viewDir.xy * param[2].z * fade; // the amount to shift the texture coordinates per layer (from vector P)
|
||||
vec2 deltaTexCoords = P / numLayers;
|
||||
|
||||
|
||||
while(currentLayerDepth < currentDepthMapValue)
|
||||
{
|
||||
currentTexCoords -= deltaTexCoords; // shift texture coordinates along direction of P
|
||||
currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b; // get depthmap value at current texture coordinates
|
||||
currentLayerDepth += layerDepth; // get depth of next layer
|
||||
}
|
||||
|
||||
vec2 prevTexCoords = currentTexCoords + deltaTexCoords; // get texture coordinates before collision (reverse operations)
|
||||
|
||||
float afterDepth = currentDepthMapValue - currentLayerDepth; // get depth after and before collision for linear interpolation
|
||||
float beforeDepth = textureGrad(normalmap, prevTexCoords, dcoorddx, dcoorddy).b - currentLayerDepth + layerDepth;
|
||||
|
||||
float weight = afterDepth / (afterDepth - beforeDepth); // interpolation of texture coordinates
|
||||
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
|
||||
|
||||
return finalTexCoords;
|
||||
#else
|
||||
float height = texture(normalmap, f_coord).b;
|
||||
vec2 p = viewDir.xy / viewDir.z * (height * (param[2].z - param[2].w) * 0.2) * fade;
|
||||
return f_coord - p;
|
||||
#endif
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
#include <common>
|
||||
#include <tonemapping.glsl>
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
#if MOTIONBLUR_ENABLED
|
||||
layout(location = 1) out vec4 out_motion;
|
||||
#endif
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color = vec4(1.0, 0.0, 1.0, 1.0);
|
||||
#if POSTFX_ENABLED
|
||||
out_color = color;
|
||||
#else
|
||||
out_color = tonemap(color);
|
||||
#endif
|
||||
#if MOTIONBLUR_ENABLED
|
||||
out_motion = vec4(0.0f);
|
||||
#endif
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
in vec3 f_normal;
|
||||
in vec2 f_coord;
|
||||
in vec4 f_pos;
|
||||
in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos;
|
||||
in vec4 f_clip_pos;
|
||||
in vec4 f_clip_future_pos;
|
||||
in vec3 TangentFragPos;
|
||||
|
||||
#include <common>
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
#if MOTIONBLUR_ENABLED
|
||||
layout(location = 1) out vec4 out_motion;
|
||||
#endif
|
||||
|
||||
#param (color, 0, 0, 4, diffuse)
|
||||
#param (diffuse, 1, 0, 1, diffuse)
|
||||
#param (specular, 1, 1, 1, specular)
|
||||
#param (reflection, 1, 2, 1, one)
|
||||
#param (glossiness, 1, 3, 1, glossiness)
|
||||
#param (height_scale, 2, 1, 1, zero)
|
||||
#param (height_offset, 2, 2, 1, zero)
|
||||
|
||||
#texture (diffuse, 0, sRGB_A)
|
||||
uniform sampler2D diffuse;
|
||||
|
||||
#texture (normalmap, 1, RGBA)
|
||||
uniform sampler2D normalmap;
|
||||
|
||||
#define PARALLAX
|
||||
#include <light_common.glsl>
|
||||
#include <apply_fog.glsl>
|
||||
#include <tonemapping.glsl>
|
||||
|
||||
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir);
|
||||
|
||||
void main()
|
||||
{
|
||||
//parallax mapping
|
||||
vec3 viewDir = normalize(vec3(0.0f, 0.0f, 0.0f) - TangentFragPos); //tangent view pos - tangent frag pos
|
||||
vec2 f_coord_p = ParallaxMapping(f_coord, viewDir);
|
||||
|
||||
vec4 tex_color = texture(diffuse, f_coord_p);
|
||||
|
||||
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
|
||||
if(alphatestfail)
|
||||
discard;
|
||||
// if (tex_color.a < opacity)
|
||||
// discard;
|
||||
|
||||
vec3 fragcolor = ambient;
|
||||
|
||||
vec3 normal;
|
||||
normal.xy = (texture(normalmap, f_coord_p).rg * 2.0 - 1.0);
|
||||
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
|
||||
vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz));
|
||||
float reflectivity = param[1].z * texture(normalmap, f_coord_p).a;
|
||||
float specularity = (tex_color.r + tex_color.g + tex_color.b) * 0.5;
|
||||
legacy_gloss();
|
||||
|
||||
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
|
||||
|
||||
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
|
||||
#if POSTFX_ENABLED
|
||||
out_color = color;
|
||||
#else
|
||||
out_color = tonemap(color);
|
||||
#endif
|
||||
#if MOTIONBLUR_ENABLED
|
||||
{
|
||||
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
|
||||
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
|
||||
|
||||
out_motion = vec4(a - b, 0.0f, 0.0f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir)
|
||||
{
|
||||
float pos_len = length(f_pos.xyz);
|
||||
// gradients of the coordinates taken here: inside the loop the fragments of a quad don't go the same way
|
||||
vec2 dcoorddx = dFdx(f_coord);
|
||||
vec2 dcoorddy = dFdy(f_coord);
|
||||
|
||||
if (pos_len > 100.0) {
|
||||
return f_coord;
|
||||
}
|
||||
// the shift fades out before the distance it stops at, so it doesn't switch off at once there
|
||||
float fade = 1.0 - smoothstep(80.0, 100.0, pos_len);
|
||||
|
||||
#if EXTRAEFFECTS_ENABLED
|
||||
const float minLayers = 8.0;
|
||||
const float maxLayers = 32.0;
|
||||
float LayersWeight = pos_len / 20.0;
|
||||
vec2 currentTexCoords = f_coord;
|
||||
float currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b;
|
||||
LayersWeight = min(abs(dot(vec3(0.0, 0.0, 1.0), viewDir)),LayersWeight);
|
||||
float numLayers = mix(maxLayers, minLayers, clamp(LayersWeight, 0.0, 1.0)); // number of depth layers
|
||||
float layerDepth = 1.0 / numLayers; // calculate the size of each layer
|
||||
float currentLayerDepth = 0.0; // depth of current layer
|
||||
vec2 P = viewDir.xy * param[2].y * fade; // the amount to shift the texture coordinates per layer (from vector P)
|
||||
vec2 deltaTexCoords = P / numLayers;
|
||||
|
||||
|
||||
while(currentLayerDepth < currentDepthMapValue)
|
||||
{
|
||||
currentTexCoords -= deltaTexCoords; // shift texture coordinates along direction of P
|
||||
currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b; // get depthmap value at current texture coordinates
|
||||
currentLayerDepth += layerDepth; // get depth of next layer
|
||||
}
|
||||
|
||||
vec2 prevTexCoords = currentTexCoords + deltaTexCoords; // get texture coordinates before collision (reverse operations)
|
||||
|
||||
float afterDepth = currentDepthMapValue - currentLayerDepth; // get depth after and before collision for linear interpolation
|
||||
float beforeDepth = textureGrad(normalmap, prevTexCoords, dcoorddx, dcoorddy).b - currentLayerDepth + layerDepth;
|
||||
|
||||
float weight = afterDepth / (afterDepth - beforeDepth); // interpolation of texture coordinates
|
||||
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
|
||||
|
||||
return finalTexCoords;
|
||||
#else
|
||||
float height = texture(normalmap, f_coord).b;
|
||||
vec2 p = viewDir.xy / viewDir.z * (height * (param[2].y - param[2].z) * 0.2) * fade;
|
||||
return f_coord - p;
|
||||
#endif
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
in vec3 f_normal;
|
||||
in vec2 f_coord;
|
||||
in vec4 f_pos;
|
||||
in mat3 f_tbn; //tangent matrix nietransponowany; mnożyć przez f_tbn dla TangentLightPos; TangentViewPos; TangentFragPos;
|
||||
in vec4 f_clip_pos;
|
||||
in vec4 f_clip_future_pos;
|
||||
in vec3 TangentFragPos;
|
||||
|
||||
#include <common>
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
#if MOTIONBLUR_ENABLED
|
||||
layout(location = 1) out vec4 out_motion;
|
||||
#endif
|
||||
|
||||
#param (color, 0, 0, 4, diffuse)
|
||||
#param (diffuse, 1, 0, 1, diffuse)
|
||||
#param (specular, 1, 1, 1, specular)
|
||||
#param (reflection, 1, 2, 1, one)
|
||||
#param (glossiness, 1, 3, 1, glossiness)
|
||||
#param (height_scale, 2, 1, 1, zero)
|
||||
#param (height_offset, 2, 2, 1, zero)
|
||||
|
||||
#texture (diffuse, 0, sRGB_A)
|
||||
uniform sampler2D diffuse;
|
||||
|
||||
#texture (normalmap, 1, RGBA)
|
||||
uniform sampler2D normalmap;
|
||||
|
||||
#texture (specgloss, 2, RGBA)
|
||||
uniform sampler2D specgloss;
|
||||
|
||||
|
||||
#define PARALLAX
|
||||
#include <light_common.glsl>
|
||||
#include <apply_fog.glsl>
|
||||
#include <tonemapping.glsl>
|
||||
|
||||
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir);
|
||||
|
||||
void main()
|
||||
{
|
||||
//parallax mapping
|
||||
vec3 viewDir = normalize(vec3(0.0f, 0.0f, 0.0f) - TangentFragPos); //tangent view pos - tangent frag pos
|
||||
vec2 f_coord_p = ParallaxMapping(f_coord, viewDir);
|
||||
|
||||
vec4 tex_color = texture(diffuse, f_coord_p);
|
||||
|
||||
bool alphatestfail = ( opacity >= 0.0 ? (tex_color.a < opacity) : (tex_color.a >= -opacity) );
|
||||
if(alphatestfail)
|
||||
discard;
|
||||
// if (tex_color.a < opacity)
|
||||
// discard;
|
||||
|
||||
vec3 fragcolor = ambient;
|
||||
|
||||
vec3 normal;
|
||||
normal.xy = (texture(normalmap, f_coord_p).rg * 2.0 - 1.0);
|
||||
normal.z = sqrt(1.0 - clamp((dot(normal.xy, normal.xy)), 0.0, 1.0));
|
||||
vec3 fragnormal = normalize(f_tbn * normalize(normal.xyz));
|
||||
float reflectivity = param[1].z * texture(normalmap, f_coord_p).a;
|
||||
float specularity = texture(specgloss, f_coord_p).r;
|
||||
glossiness = texture(specgloss, f_coord_p).g * abs(param[1].w);
|
||||
metalic = texture(specgloss, f_coord_p).b;
|
||||
|
||||
fragcolor = apply_lights(fragcolor, fragnormal, tex_color.rgb, reflectivity, specularity, shadow_tone);
|
||||
|
||||
vec4 color = vec4(apply_fog(fragcolor), tex_color.a * alpha_mult);
|
||||
#if POSTFX_ENABLED
|
||||
out_color = color;
|
||||
#else
|
||||
out_color = tonemap(color);
|
||||
#endif
|
||||
#if MOTIONBLUR_ENABLED
|
||||
{
|
||||
vec2 a = (f_clip_future_pos.xy / f_clip_future_pos.w) * 0.5 + 0.5;;
|
||||
vec2 b = (f_clip_pos.xy / f_clip_pos.w) * 0.5 + 0.5;;
|
||||
|
||||
out_motion = vec4(a - b, 0.0f, 0.0f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
vec2 ParallaxMapping(vec2 f_coord, vec3 viewDir)
|
||||
{
|
||||
float pos_len = length(f_pos.xyz);
|
||||
// gradients of the coordinates taken here: inside the loop the fragments of a quad don't go the same way
|
||||
vec2 dcoorddx = dFdx(f_coord);
|
||||
vec2 dcoorddy = dFdy(f_coord);
|
||||
|
||||
if (pos_len > 100.0) {
|
||||
return f_coord;
|
||||
}
|
||||
// the shift fades out before the distance it stops at, so it doesn't switch off at once there
|
||||
float fade = 1.0 - smoothstep(80.0, 100.0, pos_len);
|
||||
|
||||
#if EXTRAEFFECTS_ENABLED
|
||||
const float minLayers = 8.0;
|
||||
const float maxLayers = 32.0;
|
||||
float LayersWeight = pos_len / 20.0;
|
||||
vec2 currentTexCoords = f_coord;
|
||||
float currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b;
|
||||
LayersWeight = min(abs(dot(vec3(0.0, 0.0, 1.0), viewDir)),LayersWeight);
|
||||
float numLayers = mix(maxLayers, minLayers, clamp(LayersWeight, 0.0, 1.0)); // number of depth layers
|
||||
float layerDepth = 1.0 / numLayers; // calculate the size of each layer
|
||||
float currentLayerDepth = 0.0; // depth of current layer
|
||||
vec2 P = viewDir.xy * param[2].y * fade; // the amount to shift the texture coordinates per layer (from vector P)
|
||||
vec2 deltaTexCoords = P / numLayers;
|
||||
|
||||
|
||||
while(currentLayerDepth < currentDepthMapValue)
|
||||
{
|
||||
currentTexCoords -= deltaTexCoords; // shift texture coordinates along direction of P
|
||||
currentDepthMapValue = textureGrad(normalmap, currentTexCoords, dcoorddx, dcoorddy).b; // get depthmap value at current texture coordinates
|
||||
currentLayerDepth += layerDepth; // get depth of next layer
|
||||
}
|
||||
|
||||
vec2 prevTexCoords = currentTexCoords + deltaTexCoords; // get texture coordinates before collision (reverse operations)
|
||||
|
||||
float afterDepth = currentDepthMapValue - currentLayerDepth; // get depth after and before collision for linear interpolation
|
||||
float beforeDepth = textureGrad(normalmap, prevTexCoords, dcoorddx, dcoorddy).b - currentLayerDepth + layerDepth;
|
||||
|
||||
float weight = afterDepth / (afterDepth - beforeDepth); // interpolation of texture coordinates
|
||||
vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);
|
||||
|
||||
return finalTexCoords;
|
||||
#else
|
||||
float height = texture(normalmap, f_coord).b;
|
||||
vec2 p = viewDir.xy / viewDir.z * (height * (param[2].y - param[2].z) * 0.2) * fade;
|
||||
return f_coord - p;
|
||||
#endif
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// the rails and the ballast of a track faded into those of the tracks joined at its ends, see trackblend.glsl
|
||||
#texture (diffuse, 0, sRGB_A)
|
||||
uniform sampler2D diffuse;
|
||||
#texture (diffuse1, 1, sRGB_A)
|
||||
uniform sampler2D diffuse1;
|
||||
#texture (diffuse2, 2, sRGB_A)
|
||||
uniform sampler2D diffuse2;
|
||||
#texture (diffuse3, 3, sRGB_A)
|
||||
uniform sampler2D diffuse3;
|
||||
#include <trackblend.glsl>
|
||||
@@ -1,19 +0,0 @@
|
||||
// the rails and the ballast of a track of a normal mapped material faded into those of the tracks joined at its ends, see trackblend.glsl
|
||||
#define TRACKBLEND_NORMALMAP 1
|
||||
#texture (diffuse, 0, sRGB_A)
|
||||
uniform sampler2D diffuse;
|
||||
#texture (diffuse1, 1, sRGB_A)
|
||||
uniform sampler2D diffuse1;
|
||||
#texture (diffuse2, 2, sRGB_A)
|
||||
uniform sampler2D diffuse2;
|
||||
#texture (diffuse3, 3, sRGB_A)
|
||||
uniform sampler2D diffuse3;
|
||||
#texture (normalmap, 4, RGBA)
|
||||
uniform sampler2D normalmap;
|
||||
#texture (normalmap1, 5, RGBA)
|
||||
uniform sampler2D normalmap1;
|
||||
#texture (normalmap2, 6, RGBA)
|
||||
uniform sampler2D normalmap2;
|
||||
#texture (normalmap3, 7, RGBA)
|
||||
uniform sampler2D normalmap3;
|
||||
#include <trackblend.glsl>
|
||||
@@ -1,22 +0,0 @@
|
||||
// the rails and the ballast of a track of a parallax material faded into those of the tracks joined at its ends, see trackblend.glsl
|
||||
#define TRACKBLEND_NORMALMAP 1
|
||||
#define TRACKBLEND_PARALLAX 1
|
||||
#param (height_scale, 2, 1, 1, zero)
|
||||
#param (height_offset, 2, 2, 1, zero)
|
||||
#texture (diffuse, 0, sRGB_A)
|
||||
uniform sampler2D diffuse;
|
||||
#texture (diffuse1, 1, sRGB_A)
|
||||
uniform sampler2D diffuse1;
|
||||
#texture (diffuse2, 2, sRGB_A)
|
||||
uniform sampler2D diffuse2;
|
||||
#texture (diffuse3, 3, sRGB_A)
|
||||
uniform sampler2D diffuse3;
|
||||
#texture (normalmap, 4, RGBA)
|
||||
uniform sampler2D normalmap;
|
||||
#texture (normalmap1, 5, RGBA)
|
||||
uniform sampler2D normalmap1;
|
||||
#texture (normalmap2, 6, RGBA)
|
||||
uniform sampler2D normalmap2;
|
||||
#texture (normalmap3, 7, RGBA)
|
||||
uniform sampler2D normalmap3;
|
||||
#include <trackblend.glsl>
|
||||
@@ -1,18 +0,0 @@
|
||||
// the rails and the ballast of a track of a material with normal and specgloss maps faded into those of the tracks joined at its ends, see trackblend.glsl
|
||||
#define TRACKBLEND_NORMALMAP 1
|
||||
#define TRACKBLEND_SPECGLOSS 1
|
||||
#texture (diffuse, 0, sRGB_A)
|
||||
uniform sampler2D diffuse;
|
||||
#texture (diffuse1, 1, sRGB_A)
|
||||
uniform sampler2D diffuse1;
|
||||
#texture (diffuse2, 2, sRGB_A)
|
||||
uniform sampler2D diffuse2;
|
||||
#texture (normalmap, 3, RGBA)
|
||||
uniform sampler2D normalmap;
|
||||
#texture (normalmap1, 4, RGBA)
|
||||
uniform sampler2D normalmap1;
|
||||
#texture (normalmap2, 5, RGBA)
|
||||
uniform sampler2D normalmap2;
|
||||
#texture (specgloss, 6, RGBA)
|
||||
uniform sampler2D specgloss;
|
||||
#include <trackblend.glsl>
|
||||
12
shaders/ogl46/alphashadowmap.frag
Normal file
12
shaders/ogl46/alphashadowmap.frag
Normal file
@@ -0,0 +1,12 @@
|
||||
#version 460 core
|
||||
#define STAGE_FRAGMENT 1
|
||||
layout(location = 2) in vec2 f_coord;
|
||||
|
||||
#pragma texture(tex1, 0, sRGB_A)
|
||||
layout(binding = 0) uniform sampler2D tex1;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (texture(tex1, f_coord).a < 0.5f)
|
||||
discard;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// fog between the camera and a fragment, see fog_volume.glsl and weather_common.glsl. a draw with a fog_density of 0 is not
|
||||
// fogged; one with another density than the regular one (the glare of lamps) gets the optical depth scaled by the ratio
|
||||
#include <fog_volume.glsl>
|
||||
#include "fog_volume.glsl"
|
||||
|
||||
// rgb: light the fog adds, a: what it lets through
|
||||
vec4 fog_at_fragment()
|
||||
@@ -1,10 +1,12 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// what the cpu needs from the atmosphere, worked out from the sky-view table in one work group: the light arriving at the
|
||||
// observer from every direction as spherical harmonics (the ground below the horizon lit by the sun and the sky), the
|
||||
// transmittance towards the sun, and the average light of the sky above the horizon and over the whole sky.
|
||||
// run after the sky-view table; the result is read back by the cpu a few frames later
|
||||
|
||||
#include <common>
|
||||
#include <atmosphere_common.glsl>
|
||||
#include "common.glsl"
|
||||
#include "atmosphere_common.glsl"
|
||||
|
||||
layout(binding = ATMOSPHERE_TRANSMITTANCE_UNIT) uniform sampler2D atmo_transmittance_lut;
|
||||
layout(binding = ATMOSPHERE_SKYVIEW_UNIT) uniform sampler2D atmo_skyview_lut;
|
||||
@@ -1,12 +1,14 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// light scattered more than once, for the multiple scattering table (Hillaire 2020, section 5.5): for a point at a height
|
||||
// lit by the sun at an angle, the second order scattering from 64 directions around it, summed as a geometric series of
|
||||
// all the orders. one work group per texel, one invocation per direction. run after the transmittance table
|
||||
|
||||
#include <common>
|
||||
#include <atmosphere_common.glsl>
|
||||
#include "common.glsl"
|
||||
#include "atmosphere_common.glsl"
|
||||
|
||||
layout(binding = ATMOSPHERE_TRANSMITTANCE_UNIT) uniform sampler2D atmo_transmittance_lut;
|
||||
#include <atmosphere_integrate.glsl>
|
||||
#include "atmosphere_integrate.glsl"
|
||||
|
||||
layout(local_size_x = 64) in;
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// light of the sky seen by the observer in every direction, for the sky-view table (atmosphere_common.glsl), which the
|
||||
// sky, the ambient light and the fog colour are read from. one invocation per texel. run every frame the sky is drawn with
|
||||
// the atmosphere, after the transmittance and multiple scattering tables
|
||||
|
||||
#include <common>
|
||||
#include <atmosphere_common.glsl>
|
||||
#include "common.glsl"
|
||||
#include "atmosphere_common.glsl"
|
||||
|
||||
layout(binding = ATMOSPHERE_TRANSMITTANCE_UNIT) uniform sampler2D atmo_transmittance_lut;
|
||||
layout(binding = ATMOSPHERE_MULTISCATTERING_UNIT) uniform sampler2D atmo_multiscattering_lut;
|
||||
#define ATMO_MULTISCATTERING_LUT
|
||||
#include <atmosphere_integrate.glsl>
|
||||
#include "atmosphere_integrate.glsl"
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// transmittance from a point in the atmosphere to its top, for the transmittance table (atmosphere_common.glsl).
|
||||
// one invocation per texel. run when the parameters of the atmosphere change
|
||||
|
||||
#include <common>
|
||||
#include <atmosphere_common.glsl>
|
||||
#include "common.glsl"
|
||||
#include "atmosphere_common.glsl"
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
|
||||
29
shaders/ogl46/billboard.frag
Normal file
29
shaders/ogl46/billboard.frag
Normal file
@@ -0,0 +1,29 @@
|
||||
#version 460 core
|
||||
#define STAGE_FRAGMENT 1
|
||||
layout(location = 2) in vec2 f_coord;
|
||||
|
||||
#pragma texture(tex1, 0, sRGB_A)
|
||||
layout(binding = 0) uniform sampler2D tex1;
|
||||
|
||||
#include "common.glsl"
|
||||
#include "tonemapping.glsl"
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
layout(location = 1) out vec4 out_motion;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 tex_color = texture(tex1, f_coord);
|
||||
if (POSTFX_ENABLED)
|
||||
{
|
||||
out_color = tex_color * param[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
out_color = tonemap(tex_color * param[0]);
|
||||
}
|
||||
if (MOTIONBLUR_ENABLED)
|
||||
{
|
||||
out_motion = vec4(0.0f);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
#version 460 core
|
||||
#define STAGE_FRAGMENT 1
|
||||
// the sun, the moon and the textured sprite of the easter egg, drawn over the sky on a quad around the direction of the body
|
||||
// (celestial.vert). param[0]: colour (rgb) and alpha, param[1]: position in view space and half the size of the quad,
|
||||
// param[2].xy: corner (sprite) or centre (moon) of the picture in the texture, param[2].z: its size (sprite) or the radius of
|
||||
// the disc in it (moon), in texture units, param[2].w: kind of body, 0: textured sprite, 1: sun, 2: moon.
|
||||
// the colour goes out premultiplied, the alpha is how much of the sky behind the body it hides (blending ONE, ONE_MINUS_SRC_ALPHA)
|
||||
|
||||
in vec2 f_coord;
|
||||
in vec2 f_local;
|
||||
layout(location = 2) in vec2 f_coord;
|
||||
layout(location = 15) in vec2 f_local;
|
||||
|
||||
#texture (tex1, 0, sRGB_A)
|
||||
uniform sampler2D tex1;
|
||||
#pragma texture(tex1, 0, sRGB_A)
|
||||
layout(binding = 0) uniform sampler2D tex1;
|
||||
|
||||
#include <common>
|
||||
#include <tonemapping.glsl>
|
||||
#include "common.glsl"
|
||||
#include "tonemapping.glsl"
|
||||
|
||||
// shape and light of the sun and the moon, set by opengl46_renderer::Render(world_environment *):
|
||||
// [0].xy: direction to the zenith on the quad, [0].z: height over width of the disc (flattened by the refraction near the
|
||||
@@ -22,9 +24,7 @@ uniform sampler2D tex1;
|
||||
layout(location = 0) uniform vec4 body[3];
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
#if MOTIONBLUR_ENABLED
|
||||
layout(location = 1) out vec4 out_motion;
|
||||
#endif
|
||||
|
||||
void main()
|
||||
{
|
||||
@@ -72,12 +72,16 @@ void main()
|
||||
color = vec4(albedo * param[0].rgb * ((lit + body[1].w) * coverage), coverage * param[0].a);
|
||||
}
|
||||
}
|
||||
#if POSTFX_ENABLED
|
||||
out_color = color;
|
||||
#else
|
||||
out_color = vec4(tonemap(vec4(color.rgb, 1.0)).rgb, color.a);
|
||||
#endif
|
||||
#if MOTIONBLUR_ENABLED
|
||||
out_motion = vec4(0.0f);
|
||||
#endif
|
||||
if (POSTFX_ENABLED)
|
||||
{
|
||||
out_color = color;
|
||||
}
|
||||
else
|
||||
{
|
||||
out_color = vec4(tonemap(vec4(color.rgb, 1.0)).rgb, color.a);
|
||||
}
|
||||
if (MOTIONBLUR_ENABLED)
|
||||
{
|
||||
out_motion = vec4(0.0f);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
#version 460 core
|
||||
#define STAGE_VERTEX 1
|
||||
const vec2 vert[4] = vec2[]
|
||||
(
|
||||
vec2(-1.0, 1.0),
|
||||
@@ -6,11 +8,11 @@ const vec2 vert[4] = vec2[]
|
||||
vec2( 1.0, -1.0)
|
||||
);
|
||||
|
||||
out vec2 f_coord;
|
||||
layout(location = 2) out vec2 f_coord;
|
||||
// the corner of the quad, -1...1 across it
|
||||
out vec2 f_local;
|
||||
layout(location = 15) out vec2 f_local;
|
||||
|
||||
#include <common>
|
||||
#include "common.glsl"
|
||||
|
||||
void main()
|
||||
{
|
||||
@@ -2,7 +2,7 @@
|
||||
// the shape noise (3d, clouds_noise.comp) gives the lumps of a cloud, the detail noise (3d) eats into their edges, the coverage
|
||||
// noise (2d) spreads the clouds of a layer over the sky so they take up its coverage. positions are world offsets from the
|
||||
// camera, in metres, y up; the earth curves away from the camera, so far clouds sink below the horizon
|
||||
#include <weather_common.glsl>
|
||||
#include "weather_common.glsl"
|
||||
|
||||
layout(binding = CLOUD_SHAPE_TEX) uniform sampler3D cloud_shape_noise;
|
||||
layout(binding = CLOUD_DETAIL_TEX) uniform sampler3D cloud_detail_noise;
|
||||
@@ -1,11 +1,13 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// clouds of the weather ray marched through their layers (clouds_common.glsl), into an image the sky pass lays over the sky
|
||||
// (sky_weather.frag). mode 0: the view of the main camera at a part of the resolution, smoothed over frames by
|
||||
// clouds_temporal.comp; mode 1: a panorama around the camera (azimuth across, elevation up), a band of rows per frame, for the
|
||||
// environment map and the other views. rgb: the light of the clouds with the fog in front of them, a: what they let through
|
||||
// of the sky behind; the depth image gets the distance of the clouds, for the reprojection
|
||||
|
||||
#include <common>
|
||||
#include <clouds_common.glsl>
|
||||
#include "common.glsl"
|
||||
#include "clouds_common.glsl"
|
||||
|
||||
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the tiling noises the clouds and the fog are made of, made once at the start (opengl46_weather::init).
|
||||
// kind 0, the shape (3d): r perlin-worley (perlin fbm with the worley cells carved in), g b a worley fbm of rising frequency;
|
||||
// kind 1, the detail (3d): r g b worley fbm of rising frequency;
|
||||
// kind 2, the coverage (2d): r g b three perlin fbm, spread evenly over 0-1 so a threshold of 1 - c leaves a share c above it
|
||||
|
||||
#include <common>
|
||||
#include <random.glsl>
|
||||
#include "common.glsl"
|
||||
#include "random.glsl"
|
||||
|
||||
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the shadow the clouds cast on the ground: for points of the ground reference around the camera, what the clouds let through
|
||||
// towards the key light. the shading (light_common.glsl) and the fog (fog_inject.comp) look it up along the ray of the sun,
|
||||
// see cloud_shadow() in weather_common.glsl
|
||||
|
||||
#include <common>
|
||||
#include <clouds_common.glsl>
|
||||
#include "common.glsl"
|
||||
#include "clouds_common.glsl"
|
||||
|
||||
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// the clouds of the main view smoothed over frames: the image of this frame (clouds_march.comp, ray started at another
|
||||
// distance every frame) blended into the one of the previous frames, found where the clouds were by the distance of the
|
||||
// clouds, the rotation and the move of the camera. the history is held to the range of the neighbourhood of this frame,
|
||||
// so moving clouds and a turning camera don't leave trails
|
||||
|
||||
#include <common>
|
||||
#include "common.glsl"
|
||||
|
||||
layout(local_size_x = WEATHER_GROUP_SIZE, local_size_y = WEATHER_GROUP_SIZE, local_size_z = 1) in;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// bounding boxes of the light clusters in view space, see gl/cluster_grid.h
|
||||
// one invocation per cluster, run when the projection or the depth range of the pass changes
|
||||
|
||||
#include <common>
|
||||
#include "common.glsl"
|
||||
|
||||
layout(local_size_x = CLUSTER_BUILD_GROUP_SIZE) in;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#version 460 core
|
||||
#define STAGE_COMPUTE 1
|
||||
// assigns the lights to the light clusters, see gl/cluster_grid.h
|
||||
// one invocation per cluster: tests every light of light_ssbo (the sun, slot 0, is handled apart from the clusters)
|
||||
// against the bounding box of the cluster in view space, and writes the indices of the lights which reach it.
|
||||
// run every frame after the lights were updated, for the grid of the pass about to be drawn
|
||||
|
||||
#include <common>
|
||||
#include "common.glsl"
|
||||
|
||||
layout(local_size_x = CLUSTER_CULL_GROUP_SIZE) in;
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
#version 460 core
|
||||
#define STAGE_FRAGMENT 1
|
||||
// debug view of the light clusters: tints every pixel of the scene with the colour of the number of lights which
|
||||
// were assigned to its cluster (see gl/cluster_grid.h), pixels of clusters without lights stay as they are.
|
||||
// drawn over the resolved image
|
||||
|
||||
in vec2 f_coords;
|
||||
layout(location = 14) in vec2 f_coords;
|
||||
|
||||
#include <common>
|
||||
#include "common.glsl"
|
||||
|
||||
#texture (depth_tex, 0, R)
|
||||
uniform sampler2D depth_tex;
|
||||
#pragma texture(depth_tex, 0, R)
|
||||
layout(binding = 0) uniform sampler2D depth_tex;
|
||||
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user