mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 07:28:22 -10:00
Compare commits
54
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c165b82996 | ||
|
|
4f3a0d4cff | ||
|
|
8266392f00 | ||
|
|
68166ed419 | ||
|
|
fa4b755b94 | ||
|
|
b5bcfeb8d1 | ||
|
|
b1c90ea5c5 | ||
|
|
dd4a08925c | ||
|
|
647554e182 | ||
|
|
57c28798a7 | ||
|
|
3c456a088c | ||
|
|
cd48b5b500 | ||
|
|
f1812e212d | ||
|
|
817f641478 | ||
|
|
eec4a9ce3e | ||
|
|
b2de895055 | ||
|
|
7d4d61042e | ||
|
|
fd77b84c41 | ||
|
|
2166d253a8 | ||
|
|
f5b29e721b | ||
|
|
9763c7a312 | ||
|
|
49464c56f6 | ||
|
|
4dc0ca7878 | ||
|
|
2b1759e98e | ||
|
|
1d0dd687cd | ||
|
|
705d115214 | ||
|
|
a3b4386b7a | ||
|
|
246a6ee8f0 | ||
|
|
e66fe223bf | ||
|
|
e5b591141c | ||
|
|
81b9edc2e3 | ||
|
|
259a58de60 | ||
|
|
b7e7949413 | ||
|
|
32db880e94 | ||
|
|
03589b49bf | ||
|
|
c58a0cd307 | ||
|
|
0b42d0b6e7 | ||
|
|
030bbaa793 | ||
|
|
8ca1ad9bbc | ||
|
|
499b3fb38b | ||
|
|
12bddb8366 | ||
|
|
c47a8bf351 | ||
|
|
3489da442a | ||
|
|
abf796f75f | ||
|
|
89d69189c8 | ||
|
|
b8bd1daa6e | ||
|
|
bb69b1b9db | ||
|
|
9fb692f12f | ||
|
|
43ba8b859e | ||
|
|
49f4b1d9c7 | ||
|
|
9f55626272 | ||
|
|
670df9b97e | ||
|
|
9a70489dbc | ||
|
|
52fb1bdc8d |
No files matched your search
+13
-4
@@ -57,6 +57,15 @@ endif()
|
||||
|
||||
PROJECT(FS2_Open LANGUAGES ${FSO_LANGUAGES})
|
||||
|
||||
if (ANDROID)
|
||||
message(STATUS "Building for Android (NDK)")
|
||||
set(FSO_BUILD_INCLUDED_LIBS ON CACHE BOOL "" FORCE)
|
||||
set(USING_PREBUILT_LIBS ON CACHE BOOL "" FORCE)
|
||||
set(SDL3_USE_PRECOMPILED ON CACHE BOOL "" FORCE)
|
||||
set(OPENAL_USE_PRECOMPILED ON CACHE BOOL "" FORCE)
|
||||
set(USING_PREBUILT_VULKAN ON CACHE BOOL "" FORCE)
|
||||
endif()
|
||||
|
||||
# Check if the external modules exists
|
||||
IF(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib/libRocket/Build")
|
||||
message(FATAL_ERROR "External submodules could not be found. Please make sure you have updated your submodules.")
|
||||
@@ -109,8 +118,8 @@ OPTION(FSO_BUILD_TESTS "Build unit tests" OFF)
|
||||
|
||||
OPTION(FSO_DEVELOPMENT_MODE "Generate binaries in development mode, only use if you know what you're doing!" OFF)
|
||||
|
||||
IF(WIN32 OR APPLE)
|
||||
# On windows and mac the default should be to always build the included libraries
|
||||
IF(WIN32 OR APPLE OR ANDROID)
|
||||
# On windows, mac and android the default should be to always build the included libraries
|
||||
SET(FSO_BUILD_INCLUDED_LIBS_DEFAULT ON)
|
||||
ELSE()
|
||||
SET(FSO_BUILD_INCLUDED_LIBS_DEFAULT OFF)
|
||||
@@ -138,13 +147,13 @@ OPTION(FSO_BUILD_WITH_DISCORD "Build with Discord support" ON)
|
||||
|
||||
OPTION(FSO_BUILD_WITH_OPENGL "Enable compilation of the OpenGL renderer" ON)
|
||||
OPTION(FSO_BUILD_WITH_OPENGL_DEBUG "Enables debug option for OpenGL" OFF)
|
||||
OPTION(FSO_BUILD_WITH_OPENGL_ES "When building OpenGL, use OpenGL ES compatibility layer (External shaders only!)" OFF)
|
||||
OPTION(FSO_BUILD_WITH_OPENGL_ES "When building OpenGL, use OpenGL ES compatibility layer" OFF)
|
||||
|
||||
OPTION(FSO_BUILD_WITH_VULKAN "Enable compilation of the Vulkan renderer" ON)
|
||||
|
||||
if(NOT FSO_BUILD_WITH_OPENGL AND FSO_BUILD_WITH_OPENGL_ES)
|
||||
# Disable GLES if not building with OpenGL
|
||||
set(FSO_BUILD_WITH_OPENGL_ES OFF FORCE)
|
||||
set(FSO_BUILD_WITH_OPENGL_ES OFF CACHE BOOL "" FORCE)
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ if (EXISTS "${PROJECT_SOURCE_DIR}/version_override.cmake")
|
||||
endif()
|
||||
|
||||
set_if_not_defined(FSO_VERSION_MAJOR 26)
|
||||
set_if_not_defined(FSO_VERSION_MINOR 1)
|
||||
set_if_not_defined(FSO_VERSION_MINOR 3)
|
||||
set_if_not_defined(FSO_VERSION_BUILD 0)
|
||||
|
||||
# A numeric revision e.g. the CI build number
|
||||
|
||||
@@ -77,6 +77,7 @@ TARGET_LINK_LIBRARIES(code PUBLIC jansson)
|
||||
target_link_libraries(code PUBLIC anl)
|
||||
|
||||
target_link_libraries(code PUBLIC imgui)
|
||||
target_link_libraries(code PUBLIC implot)
|
||||
|
||||
IF(FSO_BUILD_WITH_OPENXR)
|
||||
target_link_libraries(code PUBLIC OpenXR::openxr_loader)
|
||||
|
||||
@@ -1693,7 +1693,7 @@ static void asteroid_do_area_effect(object *asteroid_objp)
|
||||
if ( weapon_area_calc_damage(ship_objp, &asteroid_objp->pos, asip->inner_rad, asip->outer_rad, asip->blast, asip->damage, &blast, &damage, asip->outer_rad) == -1 )
|
||||
continue;
|
||||
|
||||
ship_apply_global_damage(ship_objp, asteroid_objp, &asteroid_objp->pos, damage, asip->damage_type_idx);
|
||||
ship_apply_global_damage(ship_objp, asteroid_objp, &asteroid_objp->pos, damage, asip->damage_type_idx, DamageSource::Explosion);
|
||||
weapon_area_apply_blast(nullptr, ship_objp, &asteroid_objp->pos, blast, false);
|
||||
} // end for
|
||||
}
|
||||
|
||||
@@ -1036,6 +1036,13 @@ void bm_init() {
|
||||
bm_inited = true;
|
||||
}
|
||||
|
||||
bool bm_is_cubemap(int handle)
|
||||
{
|
||||
const BM_TYPE type = bm_get_entry(handle)->comp_type;
|
||||
return type == BM_TYPE_CUBEMAP_DDS || type == BM_TYPE_CUBEMAP_DXT1 || type == BM_TYPE_CUBEMAP_DXT3 ||
|
||||
type == BM_TYPE_CUBEMAP_DXT5;
|
||||
}
|
||||
|
||||
int bm_is_compressed(int num) {
|
||||
auto entry = bm_get_entry(num);
|
||||
BM_TYPE type = BM_TYPE_NONE;
|
||||
|
||||
@@ -594,6 +594,14 @@ void bm_get_components(ubyte *pixel, ubyte *r, ubyte *g, ubyte *b, ubyte *a);
|
||||
*/
|
||||
int bm_is_compressed(int handle);
|
||||
|
||||
/**
|
||||
* @brief Returns true if the bitmap indexed by handle was loaded from a cubemap image file
|
||||
*
|
||||
* @note Render targets return false, even cubemap ones, and so do BC7 (DX10) DDS cubemaps, whose cube flag the DDS
|
||||
* loader does not keep
|
||||
*/
|
||||
bool bm_is_cubemap(int handle);
|
||||
|
||||
/**
|
||||
* @brief Gets the correct TCACHE_TYPE for compressed graphics (uncompressed are assumed TCACHE_TYPE_NORMAL)
|
||||
*/
|
||||
|
||||
+92
-17
@@ -33,7 +33,7 @@
|
||||
#include "parse/encrypt.h"
|
||||
#include "cfilesystem.h"
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
char Cfile_root_dir[CFILE_ROOT_DIRECTORY_LEN] = "";
|
||||
@@ -185,7 +185,12 @@ int cfile_init(const char *exe_dir, const char *cdrom_dir)
|
||||
|
||||
char buf[CFILE_ROOT_DIRECTORY_LEN];
|
||||
|
||||
strncpy(buf, exe_dir, CFILE_ROOT_DIRECTORY_LEN - 1);
|
||||
#ifndef __ANDROID__
|
||||
strncpy(buf, exe_dir, CFILE_ROOT_DIRECTORY_LEN - 1);
|
||||
#else
|
||||
(void)exe_dir;
|
||||
strncpy(buf, os_android_get_working_folder_path().c_str(), CFILE_ROOT_DIRECTORY_LEN - 1);
|
||||
#endif
|
||||
|
||||
buf[CFILE_ROOT_DIRECTORY_LEN - 1] = '\0';
|
||||
|
||||
@@ -1105,28 +1110,98 @@ void cfread_string(char *buf, int n, CFILE *file)
|
||||
} while (c != 0 );
|
||||
}
|
||||
|
||||
void cfread_string_len(char *buf, int buffer_size, CFILE *file)
|
||||
{
|
||||
int len;
|
||||
len = cfread_int(file);
|
||||
Assertion( (len <= buffer_size), "String cannot fit in buffer, len: %i, buffer: %i", len, buffer_size);
|
||||
if (len) {
|
||||
cfread(buf, len, 1, file);
|
||||
Assertion(len != buffer_size || buf[len - 1] == 0, "Unterminated string does not fit in buffer");
|
||||
}
|
||||
namespace {
|
||||
struct string_len_header {
|
||||
int len = 0;
|
||||
int stored = 0;
|
||||
int start = 0;
|
||||
bool length_cut = false;
|
||||
};
|
||||
|
||||
buf[MIN(len, buffer_size - 1)] = 0;
|
||||
// a past-the-end length is clamped like any cfread, but a negative one has nothing to clamp to, so inside a
|
||||
// limited section it throws and the loader skips the rest of the section
|
||||
string_len_header read_string_len_header(CFILE* file)
|
||||
{
|
||||
string_len_header h;
|
||||
if (!cf_is_valid(file)) {
|
||||
return h;
|
||||
}
|
||||
int before = cftell(file);
|
||||
h.length_cut = cfread(&h.len, sizeof(h.len), 1, file) != 1;
|
||||
h.start = cftell(file);
|
||||
if (h.length_cut) {
|
||||
// a clean end of file reads as an empty string without a warning, as cfread_int does
|
||||
h.length_cut = (h.start != before) || (cfeof(file) == 0);
|
||||
return h;
|
||||
}
|
||||
h.len = INTEL_INT(h.len);
|
||||
if ((h.len < 0) && (file->max_read_len != 0)) {
|
||||
throw cfile::max_read_length("Attempted to read a string of negative length " + std::to_string(h.len));
|
||||
}
|
||||
h.stored = static_cast<int>(cf_check_read_size(file, static_cast<size_t>(std::max(h.len, 0))));
|
||||
return h;
|
||||
}
|
||||
|
||||
// reads count of the stored characters and leaves the file just past the whole string; false if the read failed
|
||||
bool read_string_len_body(const string_len_header& h, char* dest, int count, CFILE* file)
|
||||
{
|
||||
bool ok = (count <= 0) || (cfread(dest, count, 1, file) == 1);
|
||||
if (!ok || (h.stored > count)) {
|
||||
cfseek(file, h.start + h.stored, CF_SEEK_SET);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
// warned last, so the file position is right even if a debug build stops here
|
||||
void warn_string_len(const string_len_header& h, bool read_ok, bool cut, int buffer_size, CFILE* file)
|
||||
{
|
||||
if (h.length_cut) {
|
||||
Warning(LOCATION, "cfread_string_len: cut-off string length field in %s", cf_get_filename(file));
|
||||
} else if (!read_ok) {
|
||||
Warning(LOCATION,
|
||||
"cfread_string_len: could not read a string of %d characters in %s",
|
||||
h.stored,
|
||||
cf_get_filename(file));
|
||||
} else if (h.len != h.stored) {
|
||||
Warning(LOCATION, "cfread_string_len: corrupt string length %d in %s", h.len, cf_get_filename(file));
|
||||
} else if (cut) {
|
||||
Warning(LOCATION,
|
||||
"cfread_string_len: a string of %d bytes was cut to fit a buffer of %d in %s",
|
||||
h.len,
|
||||
buffer_size,
|
||||
cf_get_filename(file));
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void cfread_string_len(char* buf, int buffer_size, CFILE* file)
|
||||
{
|
||||
Assertion(buffer_size > 0, "cfread_string_len needs room for at least the terminator, buffer: %i", buffer_size);
|
||||
string_len_header h = read_string_len_header(file);
|
||||
if (buffer_size <= 0) {
|
||||
bool read_ok = read_string_len_body(h, buf, 0, file);
|
||||
warn_string_len(h, read_ok, h.stored > 0, buffer_size, file);
|
||||
return;
|
||||
}
|
||||
int count = std::min(h.stored, buffer_size);
|
||||
bool read_ok = read_string_len_body(h, buf, count, file);
|
||||
int kept = read_ok ? count : 0;
|
||||
bool cut = (kept == buffer_size) && (memchr(buf, 0, buffer_size) == nullptr);
|
||||
buf[std::min(kept, buffer_size - 1)] = 0;
|
||||
warn_string_len(h, read_ok, cut, buffer_size, file);
|
||||
}
|
||||
|
||||
SCP_string cfread_string_len(CFILE *file)
|
||||
{
|
||||
int len = cfread_int(file);
|
||||
string_len_header h = read_string_len_header(file);
|
||||
|
||||
SCP_string str;
|
||||
str.resize(len);
|
||||
|
||||
if (len)
|
||||
cfread(&str[0], len, 1, file);
|
||||
str.resize(h.stored);
|
||||
bool read_ok = read_string_len_body(h, str.data(), h.stored, file);
|
||||
if (!read_ok) {
|
||||
str.clear();
|
||||
}
|
||||
warn_string_len(h, read_ok, false, 0, file);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
+8
-2
@@ -320,20 +320,26 @@ void cfread_string(char *buf,int n, CFILE *file);
|
||||
/**
|
||||
* @brief Read a fixed length string that is not null-terminated, with the length stored in file.
|
||||
*
|
||||
* If it is not null-terminated, the length of the string *must* be less than the size of the buffer (to make room for one)
|
||||
*
|
||||
* Keeps at most buffer_size - 1 characters and leaves the file just past the whole string, unless it throws.
|
||||
*
|
||||
* @param buf Pre-allocated array to store string
|
||||
* @param buffer_size Size of pre-allocated array
|
||||
* @param file File to read from
|
||||
*
|
||||
* @note Appends NULL character to string (buf)
|
||||
* @note Bad data gets a warning; at a clean end of file, or with an invalid file, the string is empty and nothing is
|
||||
* warned. Inside a limited section (cf_set_max_read_len), a negative length, or a read that still passes the limit
|
||||
* once clamped to the end of the file, throws cfile::max_read_length.
|
||||
*/
|
||||
void cfread_string_len(char *buf,int buffer_size, CFILE *file);
|
||||
|
||||
/**
|
||||
* @brief Read a string from the file where the length is stored in the file
|
||||
*
|
||||
* @param file The file to read the string from
|
||||
* @return The string that was read
|
||||
*
|
||||
* @note Warns and throws like the char* overload, except that nothing is cut.
|
||||
*/
|
||||
SCP_string cfread_string_len(CFILE *file);
|
||||
|
||||
|
||||
+24
-14
@@ -193,21 +193,8 @@ int cfseek( CFILE *cfile, int offset, int where )
|
||||
}
|
||||
|
||||
|
||||
// cfread() reads from a file
|
||||
//
|
||||
// returns: returns the number of full elements read
|
||||
//
|
||||
//
|
||||
int cfread(void *buf, int elsize, int nelem, CFILE *cfile)
|
||||
size_t cf_check_read_size(CFILE* cfile, size_t size)
|
||||
{
|
||||
if(!cf_is_valid(cfile))
|
||||
return 0;
|
||||
|
||||
size_t size = elsize*nelem;
|
||||
|
||||
if(buf == NULL || size <= 0)
|
||||
return 0;
|
||||
|
||||
if ( (cfile->raw_position+size) > cfile->size ) {
|
||||
Assertion(cfile->raw_position <= cfile->size, "Invalid raw_position value detected!");
|
||||
size = cfile->size - cfile->raw_position;
|
||||
@@ -226,6 +213,29 @@ int cfread(void *buf, int elsize, int nelem, CFILE *cfile)
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
// cfread() reads from a file
|
||||
//
|
||||
// returns: returns the number of full elements read
|
||||
//
|
||||
//
|
||||
int cfread(void *buf, int elsize, int nelem, CFILE *cfile)
|
||||
{
|
||||
if(!cf_is_valid(cfile))
|
||||
return 0;
|
||||
|
||||
size_t size = elsize*nelem;
|
||||
|
||||
if(buf == nullptr || size <= 0)
|
||||
return 0;
|
||||
|
||||
size = cf_check_read_size(cfile, size);
|
||||
if (size < 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t bytes_read;
|
||||
if (cfile->data != nullptr) {
|
||||
// This is a file from memory
|
||||
|
||||
@@ -62,4 +62,9 @@ void cf_check_compression(CFILE* cfile);
|
||||
// Used to clear compression info data and free dynamic memory used by compression
|
||||
void cf_clear_compression_info(CFILE* cfile);
|
||||
|
||||
// Clamps a read of size bytes to the end of the file and returns the clamped size, or 0 at the end of the file
|
||||
// without checking the limit; throws cfile::max_read_length if the clamped read would pass the file's read limit
|
||||
// (cf_set_max_read_len).
|
||||
size_t cf_check_read_size(CFILE* cfile, size_t size);
|
||||
|
||||
#endif
|
||||
+88
-54
@@ -75,6 +75,7 @@ const char *cmdline_arg_types[] =
|
||||
enum class flag_output_type {
|
||||
Binary,
|
||||
Json_V1,
|
||||
Json_V2,
|
||||
};
|
||||
|
||||
// variables
|
||||
@@ -555,7 +556,12 @@ int Cmdline_show_pos = 0;
|
||||
int Cmdline_show_stats = 0;
|
||||
int Cmdline_save_render_targets = 0;
|
||||
bool Cmdline_window = false;
|
||||
#ifdef __ANDROID__
|
||||
// Default to borderless in the legacy window-mode path as well.
|
||||
bool Cmdline_fullscreen_window = true;
|
||||
#else
|
||||
bool Cmdline_fullscreen_window = false;
|
||||
#endif
|
||||
std::optional<std::pair<uint16_t, uint16_t>>Cmdline_window_res = std::nullopt;
|
||||
char *Cmdline_res = 0;
|
||||
char *Cmdline_center_res = 0;
|
||||
@@ -1358,51 +1364,58 @@ static void handle_unix_modlist(char **modlist, size_t *len)
|
||||
|
||||
// external entry point into this modules
|
||||
|
||||
static void json_add_version(json_t* root) {
|
||||
auto version_obj = json_object();
|
||||
|
||||
json_object_set_new(version_obj, "full", json_string(FS_VERSION_FULL));
|
||||
json_object_set_new(version_obj, "major", json_integer(FS_VERSION_MAJOR));
|
||||
json_object_set_new(version_obj, "minor", json_integer(FS_VERSION_MINOR));
|
||||
json_object_set_new(version_obj, "build", json_integer(FS_VERSION_BUILD));
|
||||
|
||||
json_object_set_new(version_obj, "has_revision", json_boolean(FS_VERSION_HAS_REVIS));
|
||||
json_object_set_new(version_obj, "revision", json_integer(FS_VERSION_REVIS));
|
||||
json_object_set_new(version_obj, "revision_str", json_string(FS_VERSION_REVIS_STR));
|
||||
|
||||
json_object_set_new(root, "version", version_obj);
|
||||
}
|
||||
|
||||
static void json_add_easy_flags(json_t* root) {
|
||||
auto easy_array = json_array();
|
||||
|
||||
for (auto& easy_flag : easy_flags) {
|
||||
json_array_append_new(easy_array, json_string(easy_flag.name));
|
||||
}
|
||||
|
||||
json_object_set_new(root, "easy_flags", easy_array);
|
||||
}
|
||||
|
||||
static void json_add_flags(json_t* root) {
|
||||
auto flags_array = json_array();
|
||||
|
||||
for (auto& flag : exe_params) {
|
||||
auto flag_obj = json_object();
|
||||
|
||||
json_object_set_new(flag_obj, "name", json_string(flag.name));
|
||||
json_object_set_new(flag_obj, "description", json_string(flag.desc));
|
||||
json_object_set_new(flag_obj, "fso_only", json_boolean(flag.fso_only));
|
||||
json_object_set_new(flag_obj, "on_flags", json_integer(flag.on_flags));
|
||||
json_object_set_new(flag_obj, "off_flags", json_integer(flag.off_flags));
|
||||
json_object_set_new(flag_obj, "type", json_string(flag.type));
|
||||
json_object_set_new(flag_obj, "web_url", json_string(flag.web_url));
|
||||
|
||||
json_array_append_new(flags_array, flag_obj);
|
||||
}
|
||||
|
||||
json_object_set_new(root, "flags", flags_array);
|
||||
}
|
||||
|
||||
static json_t* json_get_v1() {
|
||||
auto root = json_object();
|
||||
|
||||
{
|
||||
auto version_obj = json_object();
|
||||
json_add_version(root);
|
||||
json_add_easy_flags(root);
|
||||
json_add_flags(root);
|
||||
|
||||
json_object_set_new(version_obj, "full", json_string(FS_VERSION_FULL));
|
||||
json_object_set_new(version_obj, "major", json_integer(FS_VERSION_MAJOR));
|
||||
json_object_set_new(version_obj, "minor", json_integer(FS_VERSION_MINOR));
|
||||
json_object_set_new(version_obj, "build", json_integer(FS_VERSION_BUILD));
|
||||
|
||||
json_object_set_new(version_obj, "has_revision", json_boolean(FS_VERSION_HAS_REVIS));
|
||||
json_object_set_new(version_obj, "revision", json_integer(FS_VERSION_REVIS));
|
||||
json_object_set_new(version_obj, "revision_str", json_string(FS_VERSION_REVIS_STR));
|
||||
|
||||
json_object_set_new(root, "version", version_obj);
|
||||
}
|
||||
{
|
||||
auto easy_array = json_array();
|
||||
|
||||
for (auto& easy_flag : easy_flags) {
|
||||
json_array_append_new(easy_array, json_string(easy_flag.name));
|
||||
}
|
||||
|
||||
json_object_set_new(root, "easy_flags", easy_array);
|
||||
}
|
||||
{
|
||||
auto flags_array = json_array();
|
||||
|
||||
for (auto& flag : exe_params) {
|
||||
auto flag_obj = json_object();
|
||||
|
||||
json_object_set_new(flag_obj, "name", json_string(flag.name));
|
||||
json_object_set_new(flag_obj, "description", json_string(flag.desc));
|
||||
json_object_set_new(flag_obj, "fso_only", json_boolean(flag.fso_only));
|
||||
json_object_set_new(flag_obj, "on_flags", json_integer(flag.on_flags));
|
||||
json_object_set_new(flag_obj, "off_flags", json_integer(flag.off_flags));
|
||||
json_object_set_new(flag_obj, "type", json_string(flag.type));
|
||||
json_object_set_new(flag_obj, "web_url", json_string(flag.web_url));
|
||||
|
||||
json_array_append_new(flags_array, flag_obj);
|
||||
}
|
||||
|
||||
json_object_set_new(root, "flags", flags_array);
|
||||
}
|
||||
{
|
||||
auto caps_array = json_array();
|
||||
|
||||
@@ -1513,6 +1526,19 @@ static json_t* json_get_v1() {
|
||||
return root;
|
||||
}
|
||||
|
||||
// json_v2 reports version, supported flags, and the preferences path for launchers,
|
||||
// without querying graphics, audio, or joystick devices.
|
||||
static json_t* json_get_v2() {
|
||||
auto root = json_object();
|
||||
|
||||
json_add_version(root);
|
||||
json_add_easy_flags(root);
|
||||
json_add_flags(root);
|
||||
json_object_set_new(root, "pref_path", json_string(os_get_config_path().c_str()));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
static void write_flags_file() {
|
||||
FILE *fp = fopen("flags.lch","w");
|
||||
|
||||
@@ -1570,6 +1596,8 @@ static flag_output_type get_flags_output_type() {
|
||||
return flag_output_type::Binary;
|
||||
} else if (type == "json_v1") {
|
||||
return flag_output_type::Json_V1;
|
||||
} else if (type == "json_v2") {
|
||||
return flag_output_type::Json_V2;
|
||||
} else {
|
||||
// This is supposed to make it easy for the launcher to recognize an unsupported type
|
||||
printf("OUTPUT TYPE NOT SUPPORTED!\n");
|
||||
@@ -1580,17 +1608,23 @@ static flag_output_type get_flags_output_type() {
|
||||
|
||||
static void write_flags() {
|
||||
auto type = get_flags_output_type();
|
||||
switch(type) {
|
||||
case flag_output_type::Binary:
|
||||
write_flags_file();
|
||||
break;
|
||||
case flag_output_type::Json_V1:
|
||||
json_t* root = json_get_v1();
|
||||
|
||||
json_dumpf(root, stdout, JSON_INDENT(4));
|
||||
json_decref(root);
|
||||
break;
|
||||
if (type == flag_output_type::Binary) {
|
||||
write_flags_file();
|
||||
return;
|
||||
}
|
||||
|
||||
json_t* root = (type == flag_output_type::Json_V1) ? json_get_v1() : json_get_v2();
|
||||
|
||||
#ifdef __ANDROID__
|
||||
char* dumped = json_dumps(root, JSON_INDENT(4));
|
||||
os_android_set_flags_string(dumped ? dumped : "");
|
||||
free(dumped);
|
||||
#else
|
||||
json_dumpf(root, stdout, JSON_INDENT(4));
|
||||
#endif
|
||||
|
||||
json_decref(root);
|
||||
}
|
||||
|
||||
bool SetCmdlineParams()
|
||||
@@ -1787,6 +1821,7 @@ bool SetCmdlineParams()
|
||||
options::OptionsManager::instance()->setOverride("Graphics.WindowMode",
|
||||
std::unique_ptr<json_t>(json_integer(static_cast<int>(os::ViewportState::Windowed))), "-window");
|
||||
Cmdline_window = true;
|
||||
Cmdline_fullscreen_window = false;
|
||||
}
|
||||
|
||||
if ( fullscreen_window_arg.found( ) )
|
||||
@@ -2409,10 +2444,9 @@ bool SetCmdlineParams()
|
||||
if (gr_sync_validation_arg.found()) {
|
||||
// Enables the Vulkan validation layer + debug messenger + synchronization
|
||||
// validation feature (see VulkanRenderer::initializeInstance), but NOT the
|
||||
// engine-level graphics debug output: -gr_debug additionally draws debug
|
||||
// overlays (e.g. output_uniform_debug_data), which are unrelated to GPU
|
||||
// sync validation and would otherwise appear as spurious on-screen
|
||||
// artifacts. Keep this a pure GPU-validation switch.
|
||||
// engine-level graphics debug output: -gr_debug additionally feeds extra
|
||||
// stat groups into the ImGui frame profiler overlay (see gr_get_debug_stats),
|
||||
// which are unrelated to GPU sync validation. Keep this a pure GPU-validation switch.
|
||||
Cmdline_gr_sync_validation = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1322,7 +1322,7 @@ bool control_config_accept(bool API_Access)
|
||||
|
||||
// Check if a hardcoded preset with name already exists. If so, complain to user and force retry
|
||||
auto it = std::find_if(Control_config_presets.begin(), Control_config_presets.end(), [str](CC_preset& p) {
|
||||
return ((p.name == str) && ((p.type == Preset_t::tbl) || (p.type == Preset_t::hardcode)));
|
||||
return ((p.name == str) && ((p.get_type() == Preset_t::tbl) || (p.get_type() == Preset_t::hardcode)));
|
||||
});
|
||||
|
||||
if (it != Control_config_presets.end()) {
|
||||
@@ -1353,15 +1353,14 @@ bool control_config_accept(bool API_Access)
|
||||
}
|
||||
|
||||
// Pack the current bindings into a preset, then save the file
|
||||
CC_preset preset;
|
||||
preset.name = std::move(str);
|
||||
CC_preset preset(std::move(str), Preset_t::pst);
|
||||
std::copy(Control_config.begin(), Control_config.end(), std::back_inserter(preset.bindings));
|
||||
Control_config_presets.push_back(preset);
|
||||
save_preset_file(preset, true);
|
||||
|
||||
// Reload the presets from file. Do this instead of just pushing the preset to the vector direct to get
|
||||
// consistant ordering
|
||||
Control_config_presets.resize(1);
|
||||
Control_config_presets.erase(Control_config_presets.begin() + 1, Control_config_presets.end());
|
||||
load_preset_files();
|
||||
|
||||
// finally, save the new preset so that changes will get saved to this preset
|
||||
@@ -1778,7 +1777,7 @@ bool control_config_create_new_preset(const SCP_string& newName, bool overwrite)
|
||||
|
||||
// Check if a hardcoded preset with name already exists. If so, complain to user and force retry
|
||||
auto it = std::find_if(Control_config_presets.begin(), Control_config_presets.end(), [newName](CC_preset& p) {
|
||||
return (p.name == newName) && ((p.type == Preset_t::tbl) || (p.type == Preset_t::hardcode));
|
||||
return (p.name == newName) && ((p.get_type() == Preset_t::tbl) || (p.get_type() == Preset_t::hardcode));
|
||||
});
|
||||
|
||||
if (it != Control_config_presets.end()) {
|
||||
@@ -1791,9 +1790,7 @@ bool control_config_create_new_preset(const SCP_string& newName, bool overwrite)
|
||||
}
|
||||
|
||||
// Pack the current bindings into a preset, then save the file
|
||||
CC_preset preset;
|
||||
preset.name = newName;
|
||||
preset.type = Preset_t::pst;
|
||||
CC_preset preset(newName, Preset_t::pst);
|
||||
std::copy(Control_config.begin(), Control_config.end(), std::back_inserter(preset.bindings));
|
||||
|
||||
// Done with the file
|
||||
@@ -1805,7 +1802,7 @@ bool control_config_create_new_preset(const SCP_string& newName, bool overwrite)
|
||||
save_preset_file(preset, true);
|
||||
|
||||
// Reload the presets from file.
|
||||
Control_config_presets.resize(1);
|
||||
Control_config_presets.erase(Control_config_presets.begin() + 1, Control_config_presets.end());
|
||||
load_preset_files(newName);
|
||||
|
||||
// use the newly created preset
|
||||
@@ -1813,10 +1810,9 @@ bool control_config_create_new_preset(const SCP_string& newName, bool overwrite)
|
||||
|
||||
return true;
|
||||
|
||||
} else if ((clone->name != preset.name) || (clone->type != Preset_t::pst)) {
|
||||
} else if ((clone->name != preset.name) || (clone->get_type() != Preset_t::pst)) {
|
||||
// Complain and ignore if the preset names or the type differs
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return false; //should be unreachable, but just in case
|
||||
@@ -1826,7 +1822,7 @@ bool control_config_clone_preset(const CC_preset& preset, const SCP_string& newN
|
||||
|
||||
// Check if a hardcoded preset with name already exists. If so, complain to user and force retry
|
||||
auto it = std::find_if(Control_config_presets.begin(), Control_config_presets.end(), [newName](CC_preset& p) {
|
||||
return (p.name == newName) && ((p.type == Preset_t::tbl) || (p.type == Preset_t::hardcode));
|
||||
return (p.name == newName) && ((p.get_type() == Preset_t::tbl) || (p.get_type() == Preset_t::hardcode));
|
||||
});
|
||||
|
||||
if (it != Control_config_presets.end()) {
|
||||
@@ -1838,13 +1834,13 @@ bool control_config_clone_preset(const CC_preset& preset, const SCP_string& newN
|
||||
return false;
|
||||
}
|
||||
|
||||
CC_preset newPreset = preset;
|
||||
newPreset.name = newName;
|
||||
CC_preset newPreset(newName, Preset_t::pst);
|
||||
newPreset.bindings = preset.bindings;
|
||||
|
||||
bool success = save_preset_file(newPreset, false);
|
||||
|
||||
// Reload the presets from file.
|
||||
Control_config_presets.resize(1);
|
||||
Control_config_presets.erase(Control_config_presets.begin() + 1, Control_config_presets.end());
|
||||
load_preset_files(newName);
|
||||
|
||||
// use the newly cloned preset
|
||||
|
||||
@@ -572,20 +572,28 @@ public:
|
||||
* A preset, a collection of bindings for use in Control_config with an associated name
|
||||
*/
|
||||
class CC_preset {
|
||||
public:
|
||||
public:
|
||||
SCP_vector<CCB> bindings;
|
||||
SCP_string name;
|
||||
Preset_t type = Preset_t::hardcode;
|
||||
|
||||
public:
|
||||
CC_preset() = default;
|
||||
CC_preset(const CC_preset& A) = default;
|
||||
private:
|
||||
// No default value: the type is always set explicitly by the constructor
|
||||
Preset_t type;
|
||||
|
||||
CC_preset& operator=(const CC_preset&);
|
||||
/*!
|
||||
* Checks if the given preset is a duplicate of this one
|
||||
*/
|
||||
bool is_duplicate_of(CC_preset&);
|
||||
public:
|
||||
// No default constructor: every preset must be created with an explicit type
|
||||
CC_preset(SCP_string preset_name, Preset_t preset_type) : name(std::move(preset_name)), type(preset_type) {}
|
||||
CC_preset(const CC_preset&) = default;
|
||||
|
||||
// Preserve target's preset type during copy assignment
|
||||
CC_preset& operator=(const CC_preset& other);
|
||||
|
||||
Preset_t get_type() const
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
bool is_duplicate_of(const CC_preset& other) const;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -292,10 +292,8 @@ void control_config_common_init_bindings() {
|
||||
// init default preset
|
||||
Control_config_presets.clear();
|
||||
|
||||
CC_preset preset;
|
||||
CC_preset preset("default", Preset_t::hardcode);
|
||||
preset.bindings.reserve(Control_config.size());
|
||||
preset.name = "default";
|
||||
preset.type = Preset_t::hardcode;
|
||||
|
||||
for (auto &item : Control_config) {
|
||||
preset.bindings.push_back(CCB(item));
|
||||
@@ -1733,14 +1731,13 @@ size_t read_bind_1(CC_preset &preset) {
|
||||
*/
|
||||
void control_config_common_read_section(int s, bool first_override) {
|
||||
Assertion((s == 0) || (s == 1), "Expected value of s to be either 0 or 1, found %i instead.", s);
|
||||
CC_preset new_preset;
|
||||
CC_preset new_preset("", Preset_t::tbl);
|
||||
|
||||
// Set references to the default preset and bindings
|
||||
auto& default_preset = Control_config_presets[0];
|
||||
auto& default_bindings = default_preset.bindings;
|
||||
|
||||
new_preset.bindings.clear();
|
||||
new_preset.type = Preset_t::tbl;
|
||||
|
||||
if (s == 0) {
|
||||
// #ControlConfigOverride
|
||||
@@ -3002,23 +2999,22 @@ CCI_builder& CCI_builder::operator()(IoActionId action_id, short primary, short
|
||||
return *this;
|
||||
}
|
||||
|
||||
CC_preset& CC_preset::operator=(const CC_preset& A) {
|
||||
name = A.name;
|
||||
type = A.type;
|
||||
|
||||
std::copy(A.bindings.begin(), A.bindings.end(), bindings.begin());
|
||||
|
||||
CC_preset& CC_preset::operator=(const CC_preset& other)
|
||||
{
|
||||
if (this != &other) {
|
||||
bindings = other.bindings;
|
||||
name = other.name;
|
||||
// 'type' is intentionally preserved to maintain instance identity
|
||||
}
|
||||
return *this;
|
||||
};
|
||||
}
|
||||
|
||||
bool CC_preset::is_duplicate_of(CC_preset& A) {
|
||||
for (size_t i = 0; i < A.bindings.size(); ++i) {
|
||||
if (bindings[i] != A.bindings[i]) {
|
||||
// Found a binding that's different. Thus, this preset is not a duplicate
|
||||
bool CC_preset::is_duplicate_of(const CC_preset& other) const
|
||||
{
|
||||
for (size_t i = 0; i < other.bindings.size(); ++i) {
|
||||
if (bindings[i] != other.bindings[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Else, did not find any differences in the bindings. Thus, this preset is a duplicate
|
||||
return true;
|
||||
}
|
||||
@@ -74,9 +74,7 @@ void load_preset_files(SCP_string clone) {
|
||||
|
||||
|
||||
// Start reading in data
|
||||
CC_preset preset;
|
||||
preset.name = file;
|
||||
preset.type = Preset_t::pst;
|
||||
CC_preset preset(file, Preset_t::pst);
|
||||
preset.bindings.resize(Control_config.size());
|
||||
|
||||
size_t size;
|
||||
@@ -134,7 +132,7 @@ void load_preset_files(SCP_string clone) {
|
||||
if ((clone == preset.name) || (it == Control_config_presets.end())) {
|
||||
Control_config_presets.push_back(preset);
|
||||
|
||||
} else if ((it->name != preset.name) || (it->type != Preset_t::pst)) {
|
||||
} else if ((it->name != preset.name) || ((it->get_type() != Preset_t::pst))) {
|
||||
if (gameseq_get_state() == GS_STATE_CONTROL_CONFIG) {
|
||||
popup(PF_TITLE_WHITE | PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, "Preset '%s' is a duplicate of an existing preset, ignoring", preset.name.c_str());
|
||||
} else {
|
||||
@@ -173,7 +171,7 @@ bool delete_preset_file(CC_preset preset) {
|
||||
cf_delete(filename.c_str(), CF_TYPE_PLAYER_BINDS, CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
|
||||
|
||||
// Reload the presets from file.
|
||||
Control_config_presets.resize(1);
|
||||
Control_config_presets.erase(Control_config_presets.begin() + 1, Control_config_presets.end());
|
||||
load_preset_files();
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp sampler2DArray;
|
||||
#endif
|
||||
|
||||
#include "gamma.sdr"
|
||||
|
||||
layout(location=0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout (location = 0) in vec4 fragTexCoord;
|
||||
layout (location = 1) in vec4 fragColor;
|
||||
|
||||
layout (location = 0) out vec4 fragOut0;
|
||||
|
||||
layout (set = 1, binding = 1) uniform sampler2DArray baseMap;
|
||||
|
||||
layout (set = 2, binding = 0, std140) uniform genericData {
|
||||
@@ -17,8 +21,6 @@ layout (set = 2, binding = 0, std140) uniform genericData {
|
||||
in vec4 fragTexCoord;
|
||||
in vec4 fragColor;
|
||||
|
||||
out vec4 fragOut0;
|
||||
|
||||
uniform sampler2DArray baseMap;
|
||||
|
||||
layout (std140) uniform genericData {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
layout (location = 0) in vec4 vertPosition;
|
||||
layout (location = 1) in vec4 vertColor;
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
|
||||
layout(location=0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
layout(set = 1, binding = 1) uniform sampler2D bloomed;
|
||||
#else
|
||||
in vec4 fragTexCoord;
|
||||
out vec4 fragOut0;
|
||||
|
||||
uniform sampler2D bloomed;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
layout(set = 1, binding = 1) uniform sampler2D tex;
|
||||
#else
|
||||
out vec4 fragOut0;
|
||||
in vec4 fragTexCoord;
|
||||
uniform sampler2D tex;
|
||||
#endif
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
layout(set = 1, binding = 1) uniform sampler2D tex;
|
||||
#else
|
||||
in vec4 fragTexCoord;
|
||||
out vec4 fragOut0;
|
||||
uniform sampler2D tex;
|
||||
#endif
|
||||
|
||||
const float Luminance = 0.08;
|
||||
const float fMiddleGray = 0.2;
|
||||
const float fWhiteCutoff = 0.4;
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp sampler2D;
|
||||
precision highp sampler2DArray;
|
||||
#endif
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
layout(set = 1, binding = 1) uniform sampler2D tex;
|
||||
#else
|
||||
in vec4 fragTexCoord;
|
||||
out vec4 fragOut0;
|
||||
#ifdef COPY_ARRAY
|
||||
uniform sampler2DArray tex;
|
||||
#else
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
precision highp sampler2DArray;
|
||||
#endif
|
||||
|
||||
// NOTE: The technique and some of this code is based on this tutorial:
|
||||
// http://martindevans.me/game-development/2015/02/27/Drawing-Stuff-On-Other-Stuff-With-Deferred-Screenspace-Decals/
|
||||
@@ -29,9 +35,14 @@ layout (set = 1, binding = 1) uniform sampler2DArray decalTextures;
|
||||
layout (set = 1, binding = 4) uniform sampler2D gDepthBuffer;
|
||||
layout (set = 1, binding = 6) uniform sampler2D gNormalBuffer;
|
||||
#else
|
||||
out vec4 fragOut0; // Diffuse buffer
|
||||
out vec4 fragOut1; // Normal buffer
|
||||
out vec4 fragOut2; // Emissive buffer
|
||||
layout (location=0) out vec4 fragOut0; // Diffuse buffer
|
||||
#ifndef GL_ES
|
||||
layout (location=1) out vec4 fragOut1; // Normal buffer
|
||||
layout (location=2) out vec4 fragOut2; // Emissive buffer
|
||||
#else
|
||||
layout (location=2) out vec4 fragOut1; // Normal buffer -> COLOR_ATTACHMENT2
|
||||
layout (location=4) out vec4 fragOut2; // Emissive buffer -> COLOR_ATTACHMENT4
|
||||
#endif
|
||||
|
||||
flat in mat4 invModelMatrix;
|
||||
flat in vec3 decalDirection;
|
||||
@@ -124,7 +135,7 @@ vec3 getPixelNormal(vec3 frag_position, vec2 tex_coord, inout float alpha, out v
|
||||
}
|
||||
|
||||
// Make a smooth alpha transition leading up to an edge
|
||||
alpha = alpha * (1 - smoothstep(angle_fade_start, normal_angle_cutoff, angle));
|
||||
alpha = alpha * (1.0 - smoothstep(angle_fade_start, normal_angle_cutoff, angle));
|
||||
|
||||
return normal;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
// Binding 0: box vertex positions
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
#ifdef GL_ES
|
||||
#extension GL_EXT_clip_cull_distance : enable
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2DArray;
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(set = 2, binding = 0, std140)
|
||||
#else
|
||||
@@ -20,22 +27,28 @@ uniform genericData {
|
||||
uint clipEnabled;
|
||||
};
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
|
||||
layout(set = 1, binding = 1) uniform sampler2DArray baseMap;
|
||||
|
||||
layout(location = 0) in vec4 fragTexCoord;
|
||||
layout(location = 1) in vec4 fragColor;
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
layout(location = 2) in float vClipDist;
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
in float vClipDist;
|
||||
#endif
|
||||
|
||||
uniform sampler2DArray baseMap;
|
||||
|
||||
in vec4 fragTexCoord;
|
||||
in vec4 fragColor;
|
||||
out vec4 fragOut0;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -43,6 +56,11 @@ out vec4 fragOut0;
|
||||
|
||||
void main()
|
||||
{
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
if (clipEnabled != 0u && vClipDist < 0.0) {
|
||||
discard;
|
||||
}
|
||||
#endif
|
||||
vec4 baseColor = texture(baseMap, vec3(fragTexCoord.xy, float(baseMapIndex)));
|
||||
if(alphaThreshold > baseColor.a) discard;
|
||||
baseColor.rgb = (srgb == 1) ? srgb_to_linear(baseColor.rgb) : baseColor.rgb;
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
#ifdef GL_ES
|
||||
#extension GL_EXT_clip_cull_distance : enable
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(set = 2, binding = 0, std140)
|
||||
#else
|
||||
@@ -38,10 +44,15 @@ layout(location = 2) in vec4 vertTexCoord;
|
||||
|
||||
layout(location = 0) out vec4 fragTexCoord;
|
||||
layout(location = 1) out vec4 fragColor;
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
layout(location = 2) out float vClipDist;
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
out float gl_ClipDistance[1];
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
out float vClipDist;
|
||||
#endif
|
||||
|
||||
out vec4 fragTexCoord;
|
||||
in vec4 vertTexCoord;
|
||||
@@ -58,13 +69,16 @@ void main()
|
||||
gl_Position = projMatrix * modelViewMatrix * vertPosition;
|
||||
|
||||
if (clipEnabled != 0u) {
|
||||
#ifndef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
gl_ClipDistance[0] = dot(clipEquation, modelMatrix * vertPosition);
|
||||
#else
|
||||
vClipDist = dot(clipEquation, modelMatrix * vertPosition);
|
||||
#endif
|
||||
} else {
|
||||
#ifdef VULKAN
|
||||
// Vulkan has no glEnable(GL_CLIP_DISTANCE0) equivalent — clip distances are
|
||||
// always evaluated when declared in the shader. Must write a positive value
|
||||
// when clipping is disabled to prevent undefined-value vertex culling.
|
||||
#ifndef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
gl_ClipDistance[0] = 1.0;
|
||||
#endif
|
||||
#else
|
||||
vClipDist = 1.0;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
out vec4 fragOut0;
|
||||
out vec4 fragOut1;
|
||||
out vec4 fragOut2;
|
||||
out vec4 fragOut3;
|
||||
out vec4 fragOut4;
|
||||
out vec4 fragOut5;
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
#endif
|
||||
|
||||
layout (location=0) out vec4 fragOut0;
|
||||
layout (location=1) out vec4 fragOut1;
|
||||
layout (location=2) out vec4 fragOut2;
|
||||
layout (location=3) out vec4 fragOut3;
|
||||
layout (location=4) out vec4 fragOut4;
|
||||
layout (location=5) out vec4 fragOut5;
|
||||
|
||||
void main()
|
||||
{
|
||||
fragOut0 = vec4(0.0, 0.0, 0.0, 1.0); // color
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
//? #version 150
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
precision highp samplerCube;
|
||||
precision highp sampler2DArrayShadow;
|
||||
#endif
|
||||
|
||||
#include "lighting.sdr" //! #include "lighting.sdr"
|
||||
#include "gamma.sdr" //! #include "gamma.sdr"
|
||||
#include "shadows.sdr" //! #include "shadows.sdr"
|
||||
#include "z-compress.sdr" //! #include "z-compress.sdr"
|
||||
|
||||
#ifdef VULKAN
|
||||
#ifndef GL_ES
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
#else
|
||||
out vec4 fragOut0;
|
||||
layout(location = 5) out vec4 fragOut0; //COLOR_ATTACHMENTn is on location n in ES, here it uses COLOR_ATTACHMENT5
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
@@ -145,7 +153,7 @@ void GetLightInfo(vec3 position, in float alpha, in vec3 reflectDir, out vec3 li
|
||||
dist = length(lightDirOut);
|
||||
lightDist = dist;
|
||||
// Energy conservation term
|
||||
float alpha_adjust = clamp(alpha + (sourceRadius/(2*dist)), 0.0, 1.0);
|
||||
float alpha_adjust = clamp(alpha + (sourceRadius/(2.0*dist)), 0.0, 1.0);
|
||||
area_normalisation = alpha/alpha_adjust;
|
||||
area_normalisation *= area_normalisation;
|
||||
//end chunk
|
||||
@@ -188,7 +196,7 @@ void GetLightInfo(vec3 position, in float alpha, in vec3 reflectDir, out vec3 li
|
||||
float dist = length(lightDirOut);
|
||||
lightDist = dist;
|
||||
// Energy conservation term
|
||||
float alpha_adjust = min(alpha + (sourceRadius/(2*dist)), 1.0);
|
||||
float alpha_adjust = min(alpha + (sourceRadius/(2.0*dist)), 1.0);
|
||||
area_normalisation = alpha/alpha_adjust;
|
||||
// don't need to square as it's a line rather than a sphere.
|
||||
//end chunk
|
||||
@@ -235,11 +243,11 @@ void ComputeEnvLight(float alpha, float ao, vec3 light_dir, vec3 eyeDir, vec3 no
|
||||
// so let s = (2/alpha^2 - 2)
|
||||
// 1/2 log(s + 1) = 1/2 log(2/alpha^2 -1)
|
||||
|
||||
const float ENV_REZ = 512; // Ideally this would be #define'd and shader recompiled with envmap rez changes
|
||||
const float REZ_BIAS = log2(ENV_REZ * sqrt(3));
|
||||
const float ENV_REZ = 512.0; // Ideally this would be #define'd and shader recompiled with envmap rez changes
|
||||
const float REZ_BIAS = log2(ENV_REZ * sqrt(3.0));
|
||||
|
||||
float alphaSqr = alpha * alpha;
|
||||
float rough_bias = 0.5 * log2(2/alphaSqr - 1);
|
||||
float rough_bias = 0.5 * log2(2.0/alphaSqr - 1.0);
|
||||
float mip_bias = REZ_BIAS - rough_bias;
|
||||
|
||||
// Sample light, using mip bias to blur it.
|
||||
@@ -257,7 +265,7 @@ void ComputeEnvLight(float alpha, float ao, vec3 light_dir, vec3 eyeDir, vec3 no
|
||||
// Pseudo-IBL, so use k_IBL
|
||||
float k = alpha * alpha/ 2.0f;
|
||||
|
||||
float NdotL = max(dot(light_dir, normal),0);
|
||||
float NdotL = max(dot(light_dir, normal),0.0);
|
||||
|
||||
float g1vNL = GeometrySchlickGGX(NdotL, k);
|
||||
vec3 specEnvLighting = specEnvColour.rgb * fresnel * g1vNL;
|
||||
@@ -338,6 +346,6 @@ void main()
|
||||
float NdotL = clamp(dot(normal, lightDir), 0.0, 1.0);
|
||||
fragmentColor.rgb = computeLighting(specColor.rgb, diffColor, lightDir, normal.xyz, halfVec, eyeDir, roughness, fresnel, NdotL).rgb * diffuseLightColor * attenuation * area_normalisation;
|
||||
}
|
||||
|
||||
|
||||
fragOut0 = max(fragmentColor, vec4(0.0));
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
#endif
|
||||
|
||||
#include "lighting.sdr"
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp sampler2D;
|
||||
precision highp sampler2DArray;
|
||||
#endif
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec4 fragTexCoord;
|
||||
layout(location = 1) in vec4 fragColor;
|
||||
layout(location = 2) in float fragOffset;
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
layout(set = 1, binding = 1) uniform sampler2DArray baseMap;
|
||||
layout(set = 1, binding = 4) uniform sampler2D depthMap;
|
||||
layout(set = 1, binding = 5) uniform sampler2D frameBuffer;
|
||||
@@ -14,8 +20,6 @@ in vec4 fragTexCoord;
|
||||
in vec4 fragColor;
|
||||
in float fragOffset;
|
||||
|
||||
out vec4 fragOut0;
|
||||
|
||||
uniform sampler2DArray baseMap;
|
||||
uniform sampler2D depthMap;
|
||||
uniform sampler2D distMap;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec4 vertPosition;
|
||||
layout(location = 1) in vec4 vertColor;
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
precision highp sampler2DArray;
|
||||
#endif
|
||||
|
||||
#include "gamma.sdr"
|
||||
#include "z-compress.sdr"
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec4 fragTexCoord;
|
||||
layout(location = 1) in vec4 fragColor;
|
||||
layout(location = 2) in float fragRadius;
|
||||
layout(location = 3) in vec4 fragPosition;
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
layout(set = 1, binding = 1) uniform sampler2DArray baseMap;
|
||||
layout(set = 1, binding = 4) uniform sampler2D depthMap;
|
||||
#else
|
||||
@@ -18,8 +24,6 @@ in vec4 fragPosition;
|
||||
in vec4 fragTexCoord;
|
||||
in vec4 fragColor;
|
||||
|
||||
out vec4 fragOut0;
|
||||
|
||||
uniform sampler2DArray baseMap;
|
||||
uniform sampler2D depthMap;
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
#endif
|
||||
|
||||
layout (points) in;
|
||||
layout (triangle_strip, max_vertices = 4) out;
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec4 vertPosition;
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
#define M_PI 3.1415926535897932384626433832795
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp samplerCube;
|
||||
#endif
|
||||
|
||||
uniform samplerCube envmap;
|
||||
|
||||
in vec4 fragTexCoord;
|
||||
|
||||
out vec4 fragOut0;
|
||||
|
||||
layout (location = 0) out vec4 outColor;
|
||||
layout(location=0) out vec4 fragOut0;
|
||||
|
||||
void main() {
|
||||
const float theta = -fragTexCoord.x * (2.0 * M_PI);
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
|
||||
#include "gamma.sdr"
|
||||
|
||||
layout(location=0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
layout(set = 1, binding = 1) uniform sampler2D tex;
|
||||
layout(set = 1, binding = 4) uniform sampler2D depth_tex;
|
||||
#else
|
||||
in vec4 fragTexCoord;
|
||||
|
||||
out vec4 fragOut0;
|
||||
|
||||
uniform sampler2D tex;
|
||||
uniform sampler2D depth_tex;
|
||||
#endif
|
||||
@@ -40,7 +43,7 @@ void main()
|
||||
// If the scene is in fog then we change the color we operate on based on the depth of the pixel
|
||||
float depth_val = texture(depth_tex, fragTexCoord.xy).x;
|
||||
// Transform depth value from [0, 1] to [-1, 1]
|
||||
float depth_normalized = 2 * depth_val - 1;
|
||||
float depth_normalized = 2.0 * depth_val - 1.0;
|
||||
// Now we compute the depth value in projection space using the clipping plane information
|
||||
float view_depth = 2.0 * zNear * zFar / (zFar + zNear - depth_normalized * (zFar - zNear));
|
||||
|
||||
@@ -50,7 +53,7 @@ void main()
|
||||
if (isinf(view_depth)) {
|
||||
fragOut0.rgb = color_in.rgb;
|
||||
} else {
|
||||
float fog_dist = clamp(1 - pow(fog_density, view_depth - fog_start) , 0.0, 1.0);
|
||||
float fog_dist = clamp(1.0 - pow(fog_density, view_depth - fog_start) , 0.0, 1.0);
|
||||
vec3 finalFogColor = srgb_to_linear(fog_color);
|
||||
|
||||
fragOut0.rgb = mix(color_in.rgb, finalFogColor, fog_dist);
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
#ifdef GL_ES
|
||||
#ifdef GL_EXT_gpu_shader5
|
||||
#extension GL_EXT_gpu_shader5 : enable
|
||||
#define FXAA_GATHER4_ALPHA 1
|
||||
#endif
|
||||
precision highp float;
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
|
||||
#define FXAA_EARLY_EXIT 1
|
||||
#define FXAA_DISCARD 1
|
||||
#define FXAA_FAST_PIXEL_OFFSET 1 // With OpenGL 3.2 we can always do this
|
||||
@@ -500,8 +509,12 @@ layout(std140, set = 2, binding = 0) uniform genericData {
|
||||
#else
|
||||
uniform sampler2D tex0;
|
||||
in vec2 v_rcpFrame;
|
||||
#ifdef GL_ES
|
||||
in vec2 v_pos;
|
||||
#else
|
||||
noperspective in vec2 v_pos;
|
||||
out vec4 fragOut0;
|
||||
#endif
|
||||
layout(location=0)out vec4 fragOut0;
|
||||
#endif
|
||||
|
||||
void main() {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) out vec2 fragTexCoord;
|
||||
@@ -10,7 +13,11 @@ void main() {
|
||||
in vec4 vertPosition;
|
||||
|
||||
out vec2 v_rcpFrame;
|
||||
#ifdef GL_ES
|
||||
out vec2 v_pos;
|
||||
#else
|
||||
noperspective out vec2 v_pos;
|
||||
#endif
|
||||
|
||||
layout (std140) uniform genericData {
|
||||
float rt_w;
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
|
||||
layout(location=0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
// [0]=real color (passed through as-is), [1]=luma detection proxy (see
|
||||
// getAADetectionView; may be the same view as [0] when not HDR)
|
||||
layout(set = 1, binding = 1) uniform sampler2D fxaaTex[2];
|
||||
@@ -8,9 +14,9 @@ layout(set = 1, binding = 1) uniform sampler2D fxaaTex[2];
|
||||
#define lumaTex fxaaTex[1]
|
||||
#else
|
||||
in vec4 fragTexCoord;
|
||||
out vec4 fragOut0;
|
||||
uniform sampler2D tex;
|
||||
#endif
|
||||
|
||||
void main() {
|
||||
vec4 color = texture(tex, fragTexCoord.xy);
|
||||
#ifdef VULKAN
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
|
||||
#include "gamma.sdr"
|
||||
|
||||
layout(location=0)out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
layout(set = 1, binding = 1) uniform sampler2D tex;
|
||||
#else
|
||||
in vec4 fragTexCoord;
|
||||
out vec4 fragOut0;
|
||||
uniform sampler2D tex;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
#endif
|
||||
|
||||
const float SRGB_GAMMA = 2.2;
|
||||
const float SRGB_GAMMA_INVERSE = 1.0 / SRGB_GAMMA;
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
//!#version 150
|
||||
#include "gamma.sdr" //! #include "gamma.sdr"
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp samplerCube;
|
||||
#endif
|
||||
|
||||
#include "gamma.sdr"
|
||||
#define PI 3.1415926535897932384626433832795
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
layout(set = 1, binding = 1) uniform samplerCube envmap;
|
||||
#else
|
||||
uniform samplerCube envmap;
|
||||
in vec4 fragTexCoord;
|
||||
out vec4 fragOut0;
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
@@ -154,8 +160,8 @@ vec3( 0.7305, 0.6698, 0.1333)
|
||||
);
|
||||
|
||||
void main() {
|
||||
float xpos = 2 * gl_FragCoord.x/16 - 1; // remap to (-1, 1)
|
||||
float ypos = 2 * gl_FragCoord.y/16 - 1;
|
||||
float xpos = 2.0 * gl_FragCoord.x/16.0 - 1.0; // remap to (-1, 1)
|
||||
float ypos = 2.0 * gl_FragCoord.y/16.0 - 1.0;
|
||||
vec3 fragDir;
|
||||
vec4 fragCol = vec4(0.0);
|
||||
|
||||
@@ -191,5 +197,5 @@ void main() {
|
||||
vec3 sampDir = points[i] + (aPdotD - PdotD) * fragDir;
|
||||
fragCol += aPdotD * vec4(srgb_to_linear(texture(envmap, sampDir)).rgb, 1.0);
|
||||
}
|
||||
fragOut0 = linear_to_srgb(PI * fragCol * (1.0/128));
|
||||
fragOut0 = linear_to_srgb(PI * fragCol * (1.0/128.0));
|
||||
}
|
||||
@@ -57,7 +57,7 @@ vec3 ComputeGGX(vec3 specColor, vec3 diffColor, vec3 light, vec3 normal, vec3 ha
|
||||
|
||||
|
||||
|
||||
vec3 specular = distribution * fresnel * geometry / (4*dotNV*dotNL + 0.0001);
|
||||
vec3 specular = distribution * fresnel * geometry / (4.0*dotNV*dotNL + 0.0001);
|
||||
|
||||
// Diffuse term - Lambertian, kD represents energy lost to specular reflection.
|
||||
vec3 kD = vec3(1.0)-fresnel;
|
||||
@@ -70,7 +70,7 @@ vec3 ComputeGGX(vec3 specColor, vec3 diffColor, vec3 light, vec3 normal, vec3 ha
|
||||
vec3 computeLighting(vec3 specColor, vec3 diffColor, vec3 light, vec3 normal, vec3 halfVec, vec3 view, float roughness, float fresnelFactor, float dotNL)
|
||||
{
|
||||
#ifdef FLAG_LIGHT_MODEL_BLINN_PHONG
|
||||
return SpecularBlinnPhong(specColor, light, normal, halfVec, 32, fresnelFactor, dotNL);
|
||||
return SpecularBlinnPhong(specColor, light, normal, halfVec, 32.0, fresnelFactor, dotNL);
|
||||
#else
|
||||
return ComputeGGX(specColor, diffColor, light, normal, halfVec, view, roughness, fresnelFactor, dotNL);
|
||||
#endif
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
|
||||
layout(set = 1, binding = 1) uniform sampler2D textures[16];
|
||||
#define scene textures[0]
|
||||
@@ -9,8 +16,6 @@ layout(set = 1, binding = 1) uniform sampler2D textures[16];
|
||||
#else
|
||||
in vec4 fragTexCoord;
|
||||
|
||||
out vec4 fragOut0;
|
||||
|
||||
uniform sampler2D scene;
|
||||
uniform sampler2D cockpit;
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
//? #version 150
|
||||
#ifdef GL_ES
|
||||
#extension GL_EXT_clip_cull_distance: enable
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
precision highp sampler2DArray;
|
||||
precision highp sampler2DArrayShadow;
|
||||
#endif
|
||||
|
||||
#include "shadows.sdr" //! #include "shadows.sdr"
|
||||
#include "lighting.sdr" //! #include "lighting.sdr"
|
||||
#include "gamma.sdr" //! #include "gamma.sdr"
|
||||
@@ -112,6 +121,13 @@ uniform shadowCascadeParams {
|
||||
vec4 smoothness_factors[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
|
||||
};
|
||||
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
layout(location = 1) out vec4 fragOut1;
|
||||
layout(location = 2) out vec4 fragOut2;
|
||||
layout(location = 3) out vec4 fragOut3;
|
||||
layout(location = 4) out vec4 fragOut4;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(set = 1, binding = 1) uniform sampler2DArray materialTextures[16];
|
||||
#define sBasemap materialTextures[0]
|
||||
@@ -139,13 +155,12 @@ layout(location = 0) in VertexOutput {
|
||||
vec4 shadowUV[NUM_SHADOW_CASCADES];
|
||||
vec4 shadowPos;
|
||||
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_SHADOWS
|
||||
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
float vClipDist;
|
||||
#endif
|
||||
} vertIn;
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
layout(location = 1) out vec4 fragOut1;
|
||||
layout(location = 2) out vec4 fragOut2;
|
||||
layout(location = 3) out vec4 fragOut3;
|
||||
layout(location = 4) out vec4 fragOut4;
|
||||
#else
|
||||
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_DIFFUSE
|
||||
uniform sampler2DArray sBasemap;
|
||||
@@ -185,13 +200,10 @@ in VertexOutput {
|
||||
vec4 shadowUV[NUM_SHADOW_CASCADES];
|
||||
vec4 shadowPos;
|
||||
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_SHADOWS
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
float vClipDist;
|
||||
#endif
|
||||
} vertIn;
|
||||
|
||||
out vec4 fragOut0;
|
||||
out vec4 fragOut1;
|
||||
out vec4 fragOut2;
|
||||
out vec4 fragOut3;
|
||||
out vec4 fragOut4;
|
||||
#endif
|
||||
|
||||
vec3 FresnelLazarovEnv(vec3 specColor, vec3 view, vec3 normal, float gloss)
|
||||
@@ -283,6 +295,10 @@ vec3 CalculateLighting(vec3 normal, vec3 diffuseMaterial, vec3 specularMaterial,
|
||||
|
||||
void main()
|
||||
{
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
if (vertIn.vClipDist < 0.0) discard;
|
||||
#endif
|
||||
|
||||
vec3 eyeDir = vec3(normalize(-vertIn.position).xyz);
|
||||
vec2 texCoord = vertIn.texCoord.xy;
|
||||
|
||||
@@ -464,7 +480,7 @@ void main()
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_DIFFUSE
|
||||
// This code will only be used by the forward renderer so we apply the fog effect to both the emissive and the base
|
||||
// color.
|
||||
float fogDensityFinal = clamp(1 - pow(fogDensity, vertIn.fogDist) , 0.0, 1.0);
|
||||
float fogDensityFinal = clamp(1.0 - pow(fogDensity, vertIn.fogDist) , 0.0, 1.0);
|
||||
baseColor.rgb = mix(emissiveColor.rgb + baseColor.rgb, finalFogColor, fogDensityFinal);
|
||||
emissiveColor.rgb = vec3(0.0); // Zero the emissive color since it has already been added by the previous line
|
||||
specColor.rgb *= fogDensityFinal;
|
||||
@@ -509,11 +525,12 @@ void main()
|
||||
#prereplace IF_NOT_FLAG MODEL_SDR_FLAG_LIGHT
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_SPEC
|
||||
baseColor.rgb += pow(1.0 - clamp(dot(eyeDir, normal), 0.0, 1.0), 5.0 * clamp(glossData, 0.01, 1.0)) * specColor.rgb;
|
||||
glossData = 0;
|
||||
glossData = 0.0f;
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_SPEC
|
||||
// If there is no lighting then we copy the color data so far into the emissive.
|
||||
emissiveColor.rgb += baseColor.rgb;
|
||||
aoFactors.x = 0;
|
||||
|
||||
aoFactors.x = 0.0f;
|
||||
// and leave nothing for the light pass to add on top of it
|
||||
baseColor.rgb = vec3(0.0);
|
||||
specColor = vec4(0.0);
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
#ifdef GL_ES
|
||||
#extension GL_EXT_gpu_shader5: enable
|
||||
#extension GL_EXT_clip_cull_distance: enable
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
#else
|
||||
#extension GL_ARB_gpu_shader5: enable
|
||||
#endif
|
||||
|
||||
#conditional_include +"LARGE_SHADER" "main_large.sdr"
|
||||
#conditional_include -"LARGE_SHADER" "main_small.sdr"
|
||||
@@ -100,6 +107,9 @@ in VertexOutput {
|
||||
vec4 shadowUV[NUM_SHADOW_CASCADES];
|
||||
vec4 shadowPos;
|
||||
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_SHADOWS
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
float vClipDist;
|
||||
#endif
|
||||
} vertIn[];
|
||||
|
||||
out VertexOutput {
|
||||
@@ -117,6 +127,10 @@ out VertexOutput {
|
||||
vec4 shadowUV[NUM_SHADOW_CASCADES];
|
||||
vec4 shadowPos;
|
||||
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_SHADOWS
|
||||
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
float vClipDist;
|
||||
#endif
|
||||
} vertOut;
|
||||
|
||||
#ifdef MODEL_SDR_FLAG_THICK_OUTLINES
|
||||
@@ -166,8 +180,19 @@ out VertexOutput {
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_SHADOWS
|
||||
|
||||
if (use_clip_plane != 0) {
|
||||
gl_ClipDistance[0] = gl_in[vert].gl_ClipDistance[0];
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
vertOut.vClipDist = vertIn[vert].vClipDist;
|
||||
#else
|
||||
gl_ClipDistance[0] = gl_in[vert].gl_ClipDistance[0];
|
||||
#endif
|
||||
} else {
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
vertOut.vClipDist = 1.0;
|
||||
#else
|
||||
gl_ClipDistance[0] = 1.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
EmitVertex();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
|
||||
#ifndef VULKAN
|
||||
//? #version 150
|
||||
#ifdef GL_ES
|
||||
#extension GL_EXT_gpu_shader5: enable
|
||||
#extension GL_EXT_clip_cull_distance: enable
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp samplerBuffer;
|
||||
precision highp sampler2DArrayShadow;
|
||||
#else
|
||||
#extension GL_ARB_gpu_shader5: enable
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "shadows.sdr"
|
||||
|
||||
#conditional_include +"LARGE_SHADER" "main_large.sdr"
|
||||
@@ -145,6 +155,10 @@ layout(location = 0) out VertexOutput {
|
||||
vec4 shadowUV[NUM_SHADOW_CASCADES];
|
||||
vec4 shadowPos;
|
||||
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_SHADOWS
|
||||
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
float vClipDist;
|
||||
#endif
|
||||
} vertOut;
|
||||
#else
|
||||
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_TRANSFORM
|
||||
@@ -166,6 +180,10 @@ out VertexOutput {
|
||||
vec4 shadowUV[NUM_SHADOW_CASCADES];
|
||||
vec4 shadowPos;
|
||||
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_SHADOWS
|
||||
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
float vClipDist;
|
||||
#endif
|
||||
} vertOut;
|
||||
|
||||
#define TEXELS_PER_MATRIX 4
|
||||
@@ -247,12 +265,20 @@ void main()
|
||||
}
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_TRANSFORM
|
||||
|
||||
// Clip plane
|
||||
if(use_clip_plane != 0) {
|
||||
gl_ClipDistance[0] = dot(clip_equation, modelMatrix * orient * vertex);
|
||||
} else {
|
||||
gl_ClipDistance[0] = 1.0;
|
||||
}
|
||||
#ifndef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
if (use_clip_plane != 0) {
|
||||
gl_ClipDistance[0] = dot(clip_equation, modelMatrix * orient * vertex);
|
||||
} else {
|
||||
gl_ClipDistance[0] = 1.0;
|
||||
}
|
||||
#else
|
||||
if (use_clip_plane != 0) {
|
||||
vertOut.vClipDist = dot(clip_equation, modelMatrix * orient * vertex);
|
||||
} else {
|
||||
vertOut.vClipDist = 1.0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
vertOut.position = position;
|
||||
vertOut.normal = normal;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "z-compress.sdr"
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2DMS;
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
#include "z-compress.sdr"
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
layout(location = 1) out vec4 fragOut1;
|
||||
@@ -9,6 +12,9 @@ layout(location = 2) out vec4 fragOut2;
|
||||
layout(location = 3) out vec4 fragOut3;
|
||||
layout(location = 4) out vec4 fragOut4;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
|
||||
layout(set = 1, binding = 1) uniform sampler2DMS msaaTex[6];
|
||||
#define texColor msaaTex[0]
|
||||
#define texPos msaaTex[1]
|
||||
@@ -23,12 +29,7 @@ layout(std140, set = 2, binding = 0) uniform genericData {
|
||||
};
|
||||
#else
|
||||
in vec4 fragTexCoord;
|
||||
out vec4 fragOut0;
|
||||
out vec4 fragOut1;
|
||||
out vec4 fragOut2;
|
||||
out vec4 fragOut3;
|
||||
out vec4 fragOut4;
|
||||
out vec4 fragOut5;
|
||||
layout(location=5)out vec4 fragOut5;
|
||||
|
||||
uniform sampler2DMS texColor;
|
||||
uniform sampler2DMS texPos;
|
||||
@@ -176,7 +177,7 @@ float getMedianDist(ivec2 texel) {
|
||||
|
||||
//Fallback, do front first
|
||||
float getMedianDist(ivec2 texel) {
|
||||
float minDist = -1000000;
|
||||
float minDist = -1000000.0;
|
||||
for(int i = 0; i < samples; i++) {
|
||||
minDist = max(minDist, uncompress_depth_value(texelFetch(texPos, texel, i).z));
|
||||
}
|
||||
@@ -190,7 +191,7 @@ float getMedianDist(ivec2 texel) {
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 texSize = textureSize(texColor);
|
||||
vec2 texSize = vec2(textureSize(texColor));
|
||||
#ifdef VULKAN
|
||||
ivec2 texel = ivec2(texSize * fragTexCoord);
|
||||
#else
|
||||
@@ -206,7 +207,7 @@ void main()
|
||||
vec4 normal = vec4(0);
|
||||
vec4 specular = vec4(0);
|
||||
vec4 emissive = vec4(0);
|
||||
float depth = 0;
|
||||
float depth = 0.0;
|
||||
|
||||
for(int i = 0; i < SAMPLE_CNT; i++) {
|
||||
vec4 localPos = texelFetch(texPos, texel, i);
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2DArray;
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
layout (location = 0) in vec2 ftcoord;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
layout (location = 0) in vec4 vertPosition;
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
vec3 unpackNormal(vec2 normal_sample) {
|
||||
vec3 normal;
|
||||
normal.xy = normal_sample * 2.0 - 1.0;
|
||||
normal.z = sqrt(1 - normal_sample.x * normal_sample.x - normal_sample.y * normal_sample.y);
|
||||
normal.z = sqrt(1.0 - normal_sample.x * normal_sample.x - normal_sample.y * normal_sample.y);
|
||||
return normal;
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
|
||||
#include "gamma.sdr"
|
||||
|
||||
in vec4 fragTexCoord;
|
||||
in vec4 fragColor;
|
||||
out vec4 fragOut0;
|
||||
layout(location=0) out vec4 fragOut0;
|
||||
|
||||
uniform sampler2D baseMap;
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
layout(set = 1, binding = 1) uniform sampler2D tex;
|
||||
layout(set = 1, binding = 4) uniform sampler2D depth_tex;
|
||||
#else
|
||||
in vec4 fragTexCoord;
|
||||
out vec4 fragOut0;
|
||||
uniform sampler2D tex;
|
||||
uniform sampler2D depth_tex;
|
||||
#endif
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2DArray;
|
||||
#endif
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 1) in vec4 fragColor;
|
||||
layout(location = 2) in vec2 fragScreenPosition;
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
|
||||
layout(set = 1, binding = 1) uniform sampler2DArray baseMap;
|
||||
#else
|
||||
@@ -12,8 +19,6 @@ in vec2 fragTexCoord;
|
||||
in vec4 fragColor;
|
||||
in vec2 fragScreenPosition;
|
||||
|
||||
out vec4 fragOut0;
|
||||
|
||||
uniform sampler2DArray baseMap;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 vertPosition;
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
//? #version 150
|
||||
#ifdef GL_ES
|
||||
#extension GL_EXT_clip_cull_distance: enable
|
||||
precision highp float;
|
||||
#endif
|
||||
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in VertexOutput {
|
||||
float vClipDist;
|
||||
} vertIn;
|
||||
#else
|
||||
in VertexOutput {
|
||||
float vClipDist;
|
||||
} vertIn;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void main()
|
||||
{
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
if (vertIn.vClipDist < 0.0) discard;
|
||||
#endif
|
||||
}
|
||||
@@ -1,9 +1,16 @@
|
||||
//? #version 150
|
||||
#ifdef GL_ES
|
||||
#extension GL_EXT_gpu_shader5: enable
|
||||
#extension GL_EXT_clip_cull_distance: enable
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
#else
|
||||
#extension GL_ARB_gpu_shader5: enable
|
||||
#endif
|
||||
|
||||
layout (triangles) in;
|
||||
layout (triangle_strip, max_vertices = 3) out;
|
||||
#ifdef GL_ARB_gpu_shader5
|
||||
#if defined(GL_ARB_gpu_shader5) || defined(GL_EXT_gpu_shader5)
|
||||
layout(invocations = NUM_SHADOW_CASCADES) in;
|
||||
#endif
|
||||
|
||||
@@ -24,16 +31,25 @@ layout (std140) uniform shadowCascadeParams {
|
||||
};
|
||||
|
||||
in VertexOutput {
|
||||
#if !defined(GL_ARB_gpu_shader5)
|
||||
#if !defined(GL_ARB_gpu_shader5) && !defined(GL_EXT_gpu_shader5)
|
||||
float instance;
|
||||
#endif
|
||||
float clipModel;
|
||||
vec3 normal;
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
float vClipDist;
|
||||
#endif
|
||||
} vertIn[];
|
||||
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
out VertexOutput {
|
||||
float vClipDist;
|
||||
} vertOut;
|
||||
#endif
|
||||
|
||||
void main(void)
|
||||
{
|
||||
#ifdef GL_ARB_gpu_shader5
|
||||
#if defined(GL_ARB_gpu_shader5) || defined(GL_EXT_gpu_shader5)
|
||||
if (gl_InvocationID >= cascade_count) return;
|
||||
int cascade_id = cascade_offset + gl_InvocationID;
|
||||
#else
|
||||
@@ -49,7 +65,12 @@ void main(void)
|
||||
}
|
||||
|
||||
gl_Layer = cascade_id;
|
||||
gl_ClipDistance[0] = gl_in[vert].gl_ClipDistance[0];
|
||||
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
vertOut.vClipDist = vertIn[vert].vClipDist;
|
||||
#else
|
||||
gl_ClipDistance[0] = gl_in[vert].gl_ClipDistance[0];
|
||||
#endif
|
||||
|
||||
EmitVertex();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
//? #version 150
|
||||
#ifdef GL_ES
|
||||
#extension GL_EXT_gpu_shader5: enable
|
||||
#extension GL_EXT_clip_cull_distance: enable
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp samplerBuffer;
|
||||
#else
|
||||
#extension GL_ARB_gpu_shader5: enable
|
||||
|
||||
#ifndef GEOMETRY_FALLBACK
|
||||
#extension GL_ARB_shader_viewport_layer_array : enable
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec4 vertPosition;
|
||||
@@ -66,12 +73,25 @@ uniform samplerBuffer transform_tex;
|
||||
// supports writing gl_Layer from the vertex shader, see CAPABILITY_FAST_SHADOWS).
|
||||
#ifdef GEOMETRY_FALLBACK
|
||||
out VertexOutput {
|
||||
#if !defined(GL_ARB_gpu_shader5)
|
||||
#if !defined(GL_ARB_gpu_shader5) && !defined(GL_EXT_gpu_shader5)
|
||||
float instance;
|
||||
#endif
|
||||
float clipModel;
|
||||
vec3 normal;
|
||||
#ifdef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
float vClipDist;
|
||||
#endif
|
||||
} vertOut;
|
||||
#elif defined(SDR_FLAG_SW_CLIP_FALLBACK)
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) out VertexOutput {
|
||||
float vClipDist;
|
||||
} vertOut;
|
||||
#else
|
||||
out VertexOutput {
|
||||
float vClipDist;
|
||||
} vertOut;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define TEXELS_PER_MATRIX 4
|
||||
@@ -112,7 +132,7 @@ void main()
|
||||
#ifdef VULKAN
|
||||
int instanceIndex = gl_InstanceIndex;
|
||||
#else
|
||||
#if !defined(GL_ARB_gpu_shader5)
|
||||
#if !defined(GL_ARB_gpu_shader5) && !defined(GL_EXT_gpu_shader5)
|
||||
#ifdef APPLE
|
||||
vertOut.instance = float(gl_InstanceIDARB);
|
||||
#else
|
||||
@@ -133,11 +153,19 @@ void main()
|
||||
gl_Position = position;
|
||||
}
|
||||
|
||||
#ifndef SDR_FLAG_SW_CLIP_FALLBACK
|
||||
if (use_clip_plane != 0) {
|
||||
gl_ClipDistance[0] = dot(clip_equation, modelMatrix * orient * vertex);
|
||||
} else {
|
||||
gl_ClipDistance[0] = 1.0;
|
||||
}
|
||||
#else
|
||||
if (use_clip_plane != 0) {
|
||||
vertOut.vClipDist = dot(clip_equation, modelMatrix * orient * vertex);
|
||||
} else {
|
||||
vertOut.vClipDist = 1.0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef GEOMETRY_FALLBACK
|
||||
vertOut.normal = normal;
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2DArray;
|
||||
#endif
|
||||
|
||||
#include "gamma.sdr"
|
||||
|
||||
const float EMISSIVE_GAIN = 2.0;
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec4 fragImpactUV;
|
||||
layout(location = 1) in float fragNormOffset;
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
layout(set = 1, binding = 1) uniform sampler2DArray shieldMap;
|
||||
#else
|
||||
in vec4 fragImpactUV;
|
||||
in float fragNormOffset;
|
||||
|
||||
out vec4 fragOut0;
|
||||
|
||||
uniform sampler2DArray shieldMap;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec4 vertPosition;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(std140, set = 2, binding = 0) uniform genericData {
|
||||
@@ -16,13 +20,13 @@ layout (std140) uniform genericData {
|
||||
|
||||
#include "SMAA.sdr"
|
||||
|
||||
layout(location=0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 1) in vec2 fragPixcoord;
|
||||
layout(location = 2) in vec4 fragOffset[3];
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
// [0]=edgesTex, [1]=areaTex, [2]=searchTex
|
||||
layout(set = 1, binding = 1) uniform sampler2D smaaTex[3];
|
||||
#define edgesTex smaaTex[0]
|
||||
@@ -33,8 +37,6 @@ in vec2 fragTexCoord;
|
||||
in vec2 fragPixcoord;
|
||||
in vec4 fragOffset[3];
|
||||
|
||||
out vec4 fragOut0;
|
||||
|
||||
uniform sampler2D edgesTex;
|
||||
uniform sampler2D areaTex;
|
||||
uniform sampler2D searchTex;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(std140, set = 2, binding = 0) uniform genericData {
|
||||
@@ -16,19 +20,17 @@ layout (std140) uniform genericData {
|
||||
|
||||
#include "SMAA.sdr"
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 1) in vec4 fragOffset[3];
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
layout(set = 1, binding = 1) uniform sampler2D colorTex;
|
||||
#else
|
||||
in vec2 fragTexCoord;
|
||||
in vec4 fragOffset[3];
|
||||
|
||||
out vec4 fragOut0;
|
||||
|
||||
uniform sampler2D colorTex;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(std140, set = 2, binding = 0) uniform genericData {
|
||||
@@ -16,11 +20,12 @@ layout (std140) uniform genericData {
|
||||
|
||||
#include "SMAA.sdr"
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 1) in vec4 fragOffset;
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
// [0]=colorTex, [1]=blendTex
|
||||
layout(set = 1, binding = 1) uniform sampler2D smaaTex[2];
|
||||
@@ -30,8 +35,6 @@ layout(set = 1, binding = 1) uniform sampler2D smaaTex[2];
|
||||
in vec2 fragTexCoord;
|
||||
in vec4 fragOffset;
|
||||
|
||||
out vec4 fragOut0;
|
||||
|
||||
uniform sampler2D colorTex;
|
||||
uniform sampler2D blendTex;
|
||||
#endif
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
layout(set = 1, binding = 1) uniform sampler2D tex;
|
||||
#else
|
||||
in vec4 fragTexCoord;
|
||||
out vec4 fragOut0;
|
||||
uniform sampler2D tex;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
#endif
|
||||
|
||||
#include "gamma.sdr"
|
||||
|
||||
layout(location=0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
layout(set = 1, binding = 1) uniform sampler2D tex;
|
||||
#else
|
||||
in vec4 fragTexCoord;
|
||||
out vec4 fragOut0;
|
||||
uniform sampler2D tex;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp sampler2DArray;
|
||||
#endif
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec4 fragTexCoord;
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
layout(set = 1, binding = 1) uniform sampler2DArray textures[16];
|
||||
#define ytex textures[0]
|
||||
@@ -8,7 +14,6 @@ layout(set = 1, binding = 1) uniform sampler2DArray textures[16];
|
||||
#define vtex textures[2]
|
||||
#else
|
||||
in vec4 fragTexCoord;
|
||||
out vec4 fragOut0;
|
||||
uniform sampler2DArray ytex;
|
||||
uniform sampler2DArray utex;
|
||||
uniform sampler2DArray vtex;
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
#ifdef GL_ES
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
precision highp sampler3D;
|
||||
#endif
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
layout(set = 1, binding = 1) uniform sampler2D tex2D[16];
|
||||
#define composite tex2D[0]
|
||||
#define emissive tex2D[1]
|
||||
@@ -10,7 +17,7 @@ layout(set = 1, binding = 5) uniform sampler3D volume_tex;
|
||||
layout(set = 1, binding = 6) uniform sampler3D noise_volume_tex;
|
||||
#else
|
||||
in vec4 fragTexCoord;
|
||||
out vec4 fragOut0;
|
||||
|
||||
layout (binding = 0) uniform sampler2D composite;
|
||||
layout (binding = 1) uniform sampler2D emissive;
|
||||
layout (binding = 2) uniform sampler2D depth_tex;
|
||||
@@ -77,7 +84,7 @@ void main()
|
||||
#else
|
||||
vec4 eyeDirection = p_inv * vec4(fragTexCoord.xy * 2.0 - 1.0, -1, 1);
|
||||
#endif
|
||||
eyeDirection.w = 0;
|
||||
eyeDirection.w = 0.0;
|
||||
vec3 rayDirection = normalize((v_inv * eyeDirection).xyz);
|
||||
|
||||
vec4 color_in = texture(composite, fragTexCoord.xy);
|
||||
@@ -103,14 +110,14 @@ void main()
|
||||
#endif
|
||||
|
||||
//The t at which the ray enters the nebula cube
|
||||
float maxtMin = max(0,max(tMin.x, max(tMin.y, tMin.z)));
|
||||
float maxtMin = max(0.0, max(tMin.x, max(tMin.y, tMin.z)));
|
||||
//The t at which the ray leaves the nebula cube
|
||||
float mintMax = min(fragDepth, min(tMax.x, min(tMax.y, tMax.z)));
|
||||
|
||||
//The cumulative one minus alpha value of the nebula. Corresponds to the multiplier of the current step when additively drawing to a fragment for which the steps closer to the camera are already drawn.
|
||||
float cumOMAlpha = 1;
|
||||
float cumOMAlpha = 1.0;
|
||||
//The cumulative distance already travelled through the nebula.
|
||||
float cumnebdist = 0;
|
||||
float cumnebdist = 0.0;
|
||||
//The color of the current fragment
|
||||
vec3 cumcolor = vec3(0, 0, 0);
|
||||
|
||||
@@ -121,6 +128,14 @@ void main()
|
||||
|
||||
vec3 sidestep = 1.0 / vec3(textureSize(volume_tex, 0));
|
||||
|
||||
#ifdef GL_ES
|
||||
// The emissive texture has a complete mip chain generated by the engine.
|
||||
ivec2 emissiveSize = textureSize(emissive, 0);
|
||||
float maxEmissiveLod = float(findMSB(max(emissiveSize.x, emissiveSize.y)));
|
||||
#else
|
||||
float maxEmissiveLod = float(textureQueryLevels(emissive) - 1);
|
||||
#endif
|
||||
|
||||
for(float stept = maxtMin; stept < mintMax;) {
|
||||
//Step setup
|
||||
vec3 position = camera + rayDirection * stept - nebPos;
|
||||
@@ -159,7 +174,7 @@ void main()
|
||||
#ifdef NOISE
|
||||
//Mix the actual nebula color with the noise color depending on the product of the noise channels multiplied with intensity, smooth-clamped from 0 to 1
|
||||
vec3 stepcolor_neb = mix(nebulaColor, noiseColor,
|
||||
smoothstep(0, 1, (textureGrad(noise_volume_tex, position / noiseColorScale1, gradX, gradY).r + textureGrad(noise_volume_tex, position / noiseColorScale2, gradX, gradY).g) / 2.0 * noiseIntensity));
|
||||
smoothstep(0.0, 1.0, (textureGrad(noise_volume_tex, position / noiseColorScale1, gradX, gradY).r + textureGrad(noise_volume_tex, position / noiseColorScale2, gradX, gradY).g) / 2.0 * noiseIntensity));
|
||||
#else
|
||||
vec3 stepcolor_neb = nebulaColor;
|
||||
#endif
|
||||
@@ -169,19 +184,19 @@ void main()
|
||||
//From the current position in the volumetric cloud, sample towards the sun to test whether or not the current part of the nebula is bright or not
|
||||
for(int dlstep = 1; dlstep <= directionalLightSampleSteps; dlstep++){
|
||||
//TODO Sample these in a cone towards the sun, not just a line. Also add the high-distance sample for concave nebulae
|
||||
vec3 dlsteppos = (position - globalLightDirection * (dlstep * directionalLightStepSize)) / nebSize + 0.5;
|
||||
float dlstepalpha = textureGrad(volume_tex, dlsteppos, gradX, gradY).a * step(0, dlsteppos.x) * step(dlsteppos.x, 1) * step(0, dlsteppos.y) * step(dlsteppos.y, 1) * step(0, dlsteppos.z) * step(dlsteppos.z, 1);
|
||||
vec3 dlsteppos = (position - globalLightDirection * (float(dlstep) * directionalLightStepSize)) / nebSize + 0.5;
|
||||
float dlstepalpha = textureGrad(volume_tex, dlsteppos, gradX, gradY).a * step(0.0, dlsteppos.x) * step(dlsteppos.x, 1.0) * step(0.0, dlsteppos.y) * step(dlsteppos.y, 1.0) * step(0.0, dlsteppos.z) * step(dlsteppos.z, 1.0);
|
||||
directionalLightDepth += dlstepalpha * directionalLightStep;
|
||||
}
|
||||
stepcolor_diffuse *= beer_powder_norm * (1 - exp(-directionalLightDepth * 2.0)) * exp(-directionalLightDepth);
|
||||
stepcolor_diffuse *= beer_powder_norm * (1.0 - exp(-directionalLightDepth * 2.0)) * exp(-directionalLightDepth);
|
||||
|
||||
//Emissive
|
||||
cumnebdist += stepcolor_alpha * stepsize_current;
|
||||
vec3 emissive_lod = textureLod(emissive, fragTexCoord.xy, clamp(cumnebdist * emissiveSpreadFactor, 0, float(textureQueryLevels(emissive) - 1))).rgb;
|
||||
vec3 stepcolor_emissive = clamp(emissive_lod.rgb * pow(alphalim, 1.0 / (opacitydistance / ((fragDepth - stept) * emissiveFalloff + 0.01))) * emissiveIntensity, 0, 1);
|
||||
vec3 emissive_lod = textureLod(emissive, fragTexCoord.xy, clamp(cumnebdist * emissiveSpreadFactor, 0.0, maxEmissiveLod)).rgb;
|
||||
vec3 stepcolor_emissive = clamp(emissive_lod.rgb * pow(alphalim, 1.0 / (opacitydistance / ((fragDepth - stept) * emissiveFalloff + 0.01))) * emissiveIntensity, 0.0, 1.0);
|
||||
|
||||
//Step finish
|
||||
vec3 stepcolor = clamp(stepcolor_diffuse + stepcolor_emissive, 0, 1);
|
||||
vec3 stepcolor = clamp(stepcolor_diffuse + stepcolor_emissive, 0.0, 1.0);
|
||||
cumcolor += stepalpha * cumOMAlpha * stepcolor;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,23 @@ protected:
|
||||
return FALSE;
|
||||
|
||||
m_library = SDL_LoadObject(externlib);
|
||||
|
||||
#ifdef __ANDROID__
|
||||
if (m_library == NULL) {
|
||||
auto android_path = SDL_GetAndroidInternalStoragePath();
|
||||
if (android_path != nullptr) {
|
||||
SCP_string android_full_path = android_path;
|
||||
if (android_full_path.back() != DIR_SEPARATOR_CHAR) {
|
||||
android_full_path += DIR_SEPARATOR_CHAR;
|
||||
}
|
||||
android_full_path += SCP_string("natives") + DIR_SEPARATOR_CHAR + externlib;
|
||||
#ifndef NDEBUG
|
||||
mprintf(("Calling SDL_LoadObject with the following path: '%s'\n", android_full_path.c_str()));
|
||||
#endif
|
||||
m_library = SDL_LoadObject(android_full_path.c_str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// check full path as a fallback for our own libraries
|
||||
// NOTE: basePath is assumed to have a trailing slash!
|
||||
|
||||
+77
-12
@@ -22,6 +22,7 @@
|
||||
#include "material.h"
|
||||
#include "matrix.h"
|
||||
|
||||
#include "bmpman/bmpman.h"
|
||||
#include "cmdline/cmdline.h"
|
||||
#include "debugconsole/console.h"
|
||||
#include "executor/global_executors.h"
|
||||
@@ -42,6 +43,7 @@
|
||||
#include "render/3d.h"
|
||||
#include "scripting/hook_api.h"
|
||||
#include "scripting/scripting.h"
|
||||
#include "tracing/ProfilerOverlay.h"
|
||||
#include "tracing/tracing.h"
|
||||
#include "utils/boost/hash_combine.h"
|
||||
#include "utils/string_utils.h"
|
||||
@@ -289,7 +291,11 @@ GraphicsAPI gr_get_configured_render_api()
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef __ANDROID__
|
||||
os::ViewportState Gr_configured_window_state = os::ViewportState::Fullscreen;
|
||||
#else
|
||||
os::ViewportState Gr_configured_window_state = os::ViewportState::Borderless;
|
||||
#endif
|
||||
|
||||
static bool mode_change_func(os::ViewportState state, bool initial)
|
||||
{
|
||||
@@ -3259,24 +3265,38 @@ void gr_set_bitmap(int bitmap_num, int alphablend_mode, int bitblt_mode, float a
|
||||
gr_screen.current_bitmap = bitmap_num;
|
||||
}
|
||||
|
||||
static void output_uniform_debug_data()
|
||||
gr_debug_stats gr_get_debug_stats()
|
||||
{
|
||||
if (gr_screen.mode == GraphicsAPI::Stub) {
|
||||
return;
|
||||
gr_debug_stats stats;
|
||||
|
||||
if (!Cmdline_graphics_debug_output) {
|
||||
return stats;
|
||||
}
|
||||
|
||||
int line_height = gr_get_font_height() + 1;
|
||||
if (UniformBufferManager) {
|
||||
stats.uniform_buffer_valid = true;
|
||||
stats.uniform_buffer_size = UniformBufferManager->getBufferSize();
|
||||
stats.uniform_buffer_used = UniformBufferManager->getCurrentlyUsedSize();
|
||||
}
|
||||
|
||||
gr_set_color_fast(&Color_bright_white);
|
||||
gr_screen.gf_get_debug_stats(stats);
|
||||
|
||||
gr_printf_no_resize(gr_screen.center_offset_x + 20, gr_screen.center_offset_y + 160,
|
||||
"Uniform buffer size: " SIZE_T_ARG, UniformBufferManager->getBufferSize());
|
||||
gr_printf_no_resize(gr_screen.center_offset_x + 20, gr_screen.center_offset_y + 160 + line_height,
|
||||
"Currently used data: " SIZE_T_ARG, UniformBufferManager->getCurrentlyUsedSize());
|
||||
return stats;
|
||||
}
|
||||
|
||||
static bool Imgui_frame_active = false;
|
||||
|
||||
void gr_imgui_begin_frame()
|
||||
{
|
||||
if (Imgui_frame_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The stub renderer (standalone server) never assigns the ImGui entry points.
|
||||
if (!gr_screen.gf_imgui_new_frame || !ImGui::GetCurrentContext()) {
|
||||
return;
|
||||
}
|
||||
|
||||
gr_imgui_new_frame(); // renderer backend (OpenGL/Vulkan)
|
||||
ImGui_ImplSDL3_NewFrame(); // platform backend, derives the display size from the SDL window
|
||||
|
||||
@@ -3294,6 +3314,23 @@ void gr_imgui_begin_frame()
|
||||
}
|
||||
|
||||
ImGui::NewFrame();
|
||||
Imgui_frame_active = true;
|
||||
}
|
||||
|
||||
void gr_imgui_end_frame()
|
||||
{
|
||||
if (!Imgui_frame_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::Render();
|
||||
gr_imgui_render_draw_data();
|
||||
Imgui_frame_active = false;
|
||||
}
|
||||
|
||||
bool gr_imgui_frame_active()
|
||||
{
|
||||
return Imgui_frame_active;
|
||||
}
|
||||
|
||||
void gr_flip(bool execute_scripting)
|
||||
@@ -3314,9 +3351,13 @@ void gr_flip(bool execute_scripting)
|
||||
|
||||
model_process_cached_ui_render_instances();
|
||||
|
||||
if (Cmdline_graphics_debug_output) {
|
||||
output_uniform_debug_data();
|
||||
}
|
||||
// Every presented frame drains the frame profiler and contributes the overlay window to
|
||||
// this frame's ImGui pass. Doing it here rather than per game state is what keeps the
|
||||
// profiler's event buffer bounded: collection is global, so the drain has to be too.
|
||||
tracing::profiler_overlay_frame();
|
||||
|
||||
// Closes whatever ImGui frame the overlay (or the lab, or the options screen) opened.
|
||||
gr_imgui_end_frame();
|
||||
|
||||
// IMPORTANT: No rendering may happen after this point until gf_flip()/gr_setup_frame().
|
||||
// gr_reset_immediate_buffer() resets the write offset to 0, so any subsequent immediate
|
||||
@@ -3585,6 +3626,30 @@ void gr_heap_deallocate(GpuHeap heap_type, size_t data_offset)
|
||||
gpuHeap->freeGpuData(data_offset);
|
||||
}
|
||||
|
||||
gr_memory_stats gr_get_memory_stats()
|
||||
{
|
||||
gr_memory_stats stats;
|
||||
|
||||
// gpu_heaps[] entries stay null when gpu_heap_init() early-returned (GraphicsAPI::Stub, e.g.
|
||||
// a build with both graphics backends disabled), so this must not assume a live heap.
|
||||
auto vertex_heap = get_gpu_heap(GpuHeap::ModelVertex);
|
||||
auto index_heap = get_gpu_heap(GpuHeap::ModelIndex);
|
||||
if (vertex_heap != nullptr && index_heap != nullptr) {
|
||||
stats.model_heap_valid = true;
|
||||
stats.model_vertex_heap_used = vertex_heap->usedBytes();
|
||||
stats.model_vertex_heap_size = vertex_heap->bufferSize();
|
||||
stats.model_index_heap_used = index_heap->usedBytes();
|
||||
stats.model_index_heap_size = index_heap->bufferSize();
|
||||
}
|
||||
|
||||
stats.locked_bitmap_ram_valid = true;
|
||||
stats.locked_bitmap_ram_bytes = bm_texture_ram;
|
||||
|
||||
gr_screen.gf_get_memory_stats(stats);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
void gr_set_gamma(float gamma)
|
||||
{
|
||||
if (gr_screen.mode == GraphicsAPI::Stub) {
|
||||
|
||||
@@ -690,6 +690,56 @@ enum class BufferUsageHint { Static, Dynamic, Streaming, PersistentMapping };
|
||||
*/
|
||||
typedef void* gr_sync;
|
||||
|
||||
/**
|
||||
* @brief Per-backend diagnostic counters, populated only when -gr_debug is active
|
||||
*
|
||||
* Each stat group carries its own "valid" flag since not every backend collects every
|
||||
* group (e.g. per-draw-call counters currently only exist in the Vulkan backend). See
|
||||
* gr_get_debug_stats().
|
||||
*/
|
||||
struct gr_debug_stats {
|
||||
bool uniform_buffer_valid = false;
|
||||
size_t uniform_buffer_size = 0;
|
||||
size_t uniform_buffer_used = 0;
|
||||
|
||||
bool draw_stats_valid = false;
|
||||
int draw_calls = 0;
|
||||
int draw_indexed_calls = 0;
|
||||
int total_vertices = 0;
|
||||
int total_indices = 0;
|
||||
int apply_material_calls = 0;
|
||||
int apply_material_failures = 0;
|
||||
int no_pipeline_skips = 0;
|
||||
int descriptor_sets_allocated = 0;
|
||||
int descriptor_writes = 0;
|
||||
size_t pipeline_count = 0;
|
||||
int on_demand_texture_uploads = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Cross-backend memory usage snapshot for the profiler overlay's memory panel
|
||||
*
|
||||
* Unlike gr_debug_stats, this is always populated when queried (no -gr_debug gate) -- memory
|
||||
* usage is meant to be visible to anyone with the profiler overlay open. Each group still carries
|
||||
* its own "valid" flag since not every backend/build collects every group (e.g. both graphics
|
||||
* backends disabled at build time). See gr_get_memory_stats().
|
||||
*/
|
||||
struct gr_memory_stats {
|
||||
bool model_heap_valid = false;
|
||||
size_t model_vertex_heap_used = 0;
|
||||
size_t model_vertex_heap_size = 0;
|
||||
size_t model_index_heap_used = 0;
|
||||
size_t model_index_heap_size = 0;
|
||||
|
||||
bool locked_bitmap_ram_valid = false;
|
||||
size_t locked_bitmap_ram_bytes = 0;
|
||||
|
||||
bool gpu_purpose_valid = false;
|
||||
size_t gpu_texture_bytes = 0;
|
||||
size_t gpu_geometry_bytes = 0;
|
||||
size_t gpu_render_target_bytes = 0;
|
||||
};
|
||||
|
||||
typedef struct screen {
|
||||
int max_w = 0, max_h = 0; // Width and height
|
||||
int max_w_unscaled = 0, max_h_unscaled = 0;
|
||||
@@ -963,6 +1013,14 @@ typedef struct screen {
|
||||
std::function<void(const char* name)> gf_push_debug_group;
|
||||
std::function<void()> gf_pop_debug_group;
|
||||
|
||||
// Fills in whichever debug_stats groups this backend collects. Defaults to a no-op
|
||||
// so backends without per-draw-call counters (OpenGL, stub) don't have to assign it.
|
||||
std::function<void(gr_debug_stats& stats)> gf_get_debug_stats = [](gr_debug_stats&) {};
|
||||
|
||||
// Fills in whichever memory_stats groups this backend collects (GPU-purpose byte totals).
|
||||
// Defaults to a no-op so backends without per-purpose tagging don't have to assign it.
|
||||
std::function<void(gr_memory_stats& stats)> gf_get_memory_stats = [](gr_memory_stats&) {};
|
||||
|
||||
std::function<int()> gf_create_query_object;
|
||||
std::function<void(int obj, QueryType type)> gf_query_value;
|
||||
std::function<bool(int obj)> gf_query_value_available;
|
||||
@@ -1161,6 +1219,23 @@ bool gr_is_screenshot_requested();
|
||||
//#define gr_flip GR_CALL(gr_screen.gf_flip)
|
||||
void gr_flip(bool execute_scripting = true);
|
||||
|
||||
/**
|
||||
* @brief Collects whichever graphics-API debug stats are available for the active backend
|
||||
*
|
||||
* Returns a default-constructed (all-invalid) gr_debug_stats unless -gr_debug is active. Safe
|
||||
* to call every frame regardless of backend or debug flag.
|
||||
*/
|
||||
gr_debug_stats gr_get_debug_stats();
|
||||
|
||||
/**
|
||||
* @brief Collects whichever cross-backend memory usage stats are available
|
||||
*
|
||||
* Unlike gr_get_debug_stats(), this is always populated (no -gr_debug gate) -- memory usage is
|
||||
* meant to be visible whenever the profiler overlay is open. Safe to call every frame regardless
|
||||
* of backend or build configuration; groups a backend/build doesn't support stay invalid/zero.
|
||||
*/
|
||||
gr_memory_stats gr_get_memory_stats();
|
||||
|
||||
inline void gr_setup_frame() {
|
||||
gr_screen.gf_setup_frame();
|
||||
}
|
||||
@@ -1318,6 +1393,16 @@ inline void gr_post_process_restore_zbuffer()
|
||||
*/
|
||||
void gr_imgui_begin_frame();
|
||||
|
||||
/**
|
||||
* @brief Renders and submits the open ImGui frame, if any. Called by gr_flip().
|
||||
*/
|
||||
void gr_imgui_end_frame();
|
||||
|
||||
/**
|
||||
* @brief Whether an ImGui frame is currently open for contributions
|
||||
*/
|
||||
bool gr_imgui_frame_active();
|
||||
|
||||
inline void gr_render_primitives(material* material_info,
|
||||
primitive_type prim_type,
|
||||
vertex_layout* layout,
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#define GL_CLAMP_VERTEX_COLOR 0x891A
|
||||
#define GL_CLAMP_FRAGMENT_COLOR 0x891B
|
||||
#define GL_FIXED_ONLY 0x891D
|
||||
#define GLAD_GL_ARB_shader_viewport_layer_array 0 // Disabled, no gl_layer in vertex shader //0x8DAE
|
||||
|
||||
//Enums Redefinitions
|
||||
#define GL_CLIP_DISTANCE0 GL_CLIP_DISTANCE0_EXT // GL_EXT_clip_cull_distance
|
||||
@@ -48,6 +49,7 @@
|
||||
#define GLAD_GL_ARB_texture_storage GL_TRUE // ES 3.2
|
||||
#define GLAD_GL_ARB_draw_buffers_blend GL_TRUE // ES 3.2 (glBlendFunci and glBlendEquationi)
|
||||
#define GLAD_GL_ARB_vertex_attrib_binding GL_TRUE // ES 3.1
|
||||
#define GL_DEPTH_CLAMP GL_DEPTH_CLAMP_EXT
|
||||
|
||||
//Functions Redefinitions
|
||||
#define glBlendFunciARB glBlendFunci // ES 3.2
|
||||
@@ -234,9 +236,7 @@ inline void glDrawBuffer(GLenum buf)
|
||||
}
|
||||
}
|
||||
|
||||
// does not exist on ES, ES uses layout(location=) directly on shader
|
||||
// Notes:
|
||||
// -This function is not required to run.
|
||||
// ES uses layout(location = ) directly on shader
|
||||
#define glBindFragDataLocation(program, colorNumber, name) ((void)0)
|
||||
|
||||
// glPolygonMode() is not supported on ES, no wireframe, GL_FILL is default, GL_POINTS, and GL_LINES needs an alternative path
|
||||
@@ -254,6 +254,7 @@ static inline void glQueryCounter(GLuint id, GLenum target)
|
||||
#else
|
||||
(void)id;
|
||||
#endif
|
||||
(void)target;
|
||||
}
|
||||
|
||||
// glGetCompressedTexImage not present on GLES and no equivalent
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <direct.h>
|
||||
#endif
|
||||
|
||||
#if !defined __APPLE_CC__ && defined SCP_UNIX
|
||||
#if !defined __APPLE_CC__ && !defined __ANDROID__ && defined SCP_UNIX
|
||||
#include<glad/glad_glx.h>
|
||||
//Required because X defines none and always, which is used later
|
||||
#undef None
|
||||
@@ -1164,6 +1164,7 @@ void gr_opengl_init_function_pointers()
|
||||
|
||||
gr_screen.gf_is_capable = gr_opengl_is_capable;
|
||||
gr_screen.gf_get_property = gr_opengl_get_property;
|
||||
gr_screen.gf_get_memory_stats = gr_opengl_get_memory_stats;
|
||||
|
||||
gr_screen.gf_push_debug_group = gr_opengl_push_debug_group;
|
||||
gr_screen.gf_pop_debug_group = gr_opengl_pop_debug_group;
|
||||
@@ -1394,7 +1395,7 @@ bool gr_opengl_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps)
|
||||
Error(LOCATION, "Failed to load OpenGL!");
|
||||
}
|
||||
|
||||
#if !defined __APPLE_CC__ && defined SCP_UNIX
|
||||
#if !defined __APPLE_CC__ && !defined __ANDROID__ && defined SCP_UNIX
|
||||
if (!gladLoadGLXLoader(GL_context->getLoaderFunction(), nullptr, 0)) {
|
||||
Error(LOCATION, "Failed to load GLX!");
|
||||
}
|
||||
@@ -1453,7 +1454,9 @@ bool gr_opengl_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps)
|
||||
"Deferred lightning is enabled in settings but the gpu does not support the minimum of 6 draw buffers. "
|
||||
"This will result in rendering errors.");
|
||||
}
|
||||
|
||||
|
||||
mprintf((" Depth Clamp support : %s\n", GLAD_GL_EXT_depth_clamp ? NOX("YES") : NOX("NO")));
|
||||
mprintf((" HW Cull distance support : %s\n", GLAD_GL_EXT_clip_cull_distance ? NOX("YES") : NOX("NO")));
|
||||
mprintf((" Precompiled shaders support : %s\n", GLAD_GL_OES_get_program_binary ? NOX("YES") : NOX("NO")));
|
||||
mprintf((" Immutable buffer storage support : %s\n", GLAD_GL_EXT_buffer_storage ? NOX("YES") : NOX("NO")));
|
||||
mprintf((" BGRA8888 format support: %s\n", GLAD_GL_EXT_texture_format_BGRA8888 ? NOX("YES") : NOX("NO")));
|
||||
|
||||
@@ -760,6 +760,66 @@ void opengl_scene_texture_shutdown()
|
||||
Scene_framebuffer_in_frame = false;
|
||||
}
|
||||
|
||||
size_t opengl_get_scene_render_target_bytes()
|
||||
{
|
||||
// The bytes for each pixel come from the internal formats that opengl_setup_scene_textures()
|
||||
// gives to these textures: GL_RGBA16F holds 8 bytes, GL_RGBA8 and GL_DEPTH24_STENCIL8 hold 4.
|
||||
// Keep the values below equal to the formats at the creation sites in that function.
|
||||
constexpr size_t RGBA16F_BYTES = 8;
|
||||
constexpr size_t RGBA8_BYTES = 4;
|
||||
constexpr size_t DEPTH24_STENCIL8_BYTES = 4;
|
||||
|
||||
size_t bytes = 0;
|
||||
|
||||
auto add_texture = [&bytes](GLuint texture_id, size_t width, size_t height, size_t bytes_per_pixel,
|
||||
size_t samples = 1) {
|
||||
if (texture_id == 0) {
|
||||
return;
|
||||
}
|
||||
bytes += width * height * bytes_per_pixel * samples;
|
||||
};
|
||||
|
||||
const auto scene_w = static_cast<size_t>(Scene_texture_width);
|
||||
const auto scene_h = static_cast<size_t>(Scene_texture_height);
|
||||
|
||||
add_texture(Scene_color_texture, scene_w, scene_h, RGBA16F_BYTES);
|
||||
add_texture(Scene_ldr_texture, scene_w, scene_h, RGBA8_BYTES);
|
||||
add_texture(Scene_position_texture, scene_w, scene_h, RGBA16F_BYTES);
|
||||
add_texture(Scene_normal_texture, scene_w, scene_h, RGBA16F_BYTES);
|
||||
add_texture(Scene_specular_texture, scene_w, scene_h, RGBA8_BYTES);
|
||||
add_texture(Scene_emissive_texture, scene_w, scene_h, RGBA16F_BYTES);
|
||||
add_texture(Scene_composite_texture, scene_w, scene_h, RGBA16F_BYTES);
|
||||
add_texture(Scene_luminance_texture, scene_w, scene_h, RGBA16F_BYTES);
|
||||
add_texture(Cockpit_depth_texture, scene_w, scene_h, DEPTH24_STENCIL8_BYTES);
|
||||
add_texture(Scene_depth_texture, scene_w, scene_h, DEPTH24_STENCIL8_BYTES);
|
||||
|
||||
// The multisample textures hold one value for each sample, thus their bytes go up with the
|
||||
// MSAA level. Cmdline_msaa_enabled holds the number of samples and is 0 when MSAA is off.
|
||||
const auto samples = static_cast<size_t>(std::max(1, Cmdline_msaa_enabled));
|
||||
|
||||
add_texture(Scene_color_texture_ms, scene_w, scene_h, RGBA16F_BYTES, samples);
|
||||
add_texture(Scene_position_texture_ms, scene_w, scene_h, RGBA16F_BYTES, samples);
|
||||
add_texture(Scene_normal_texture_ms, scene_w, scene_h, RGBA16F_BYTES, samples);
|
||||
add_texture(Scene_specular_texture_ms, scene_w, scene_h, RGBA8_BYTES, samples);
|
||||
add_texture(Scene_emissive_texture_ms, scene_w, scene_h, RGBA16F_BYTES, samples);
|
||||
add_texture(Scene_depth_texture_ms, scene_w, scene_h, DEPTH24_STENCIL8_BYTES, samples);
|
||||
|
||||
// The back buffer and gamma blit textures use the size of the screen, not the (clamped) size
|
||||
// of the scene textures.
|
||||
const auto screen_w = static_cast<size_t>(gr_screen.max_w);
|
||||
const auto screen_h = static_cast<size_t>(gr_screen.max_h);
|
||||
|
||||
add_texture(Back_texture, screen_w, screen_h, RGBA16F_BYTES);
|
||||
add_texture(Back_depth_texture, screen_w, screen_h, DEPTH24_STENCIL8_BYTES);
|
||||
add_texture(GammaBlit_texture, screen_w, screen_h, RGBA8_BYTES);
|
||||
|
||||
// The two distortion textures always have the size 32x32.
|
||||
add_texture(Distortion_texture[0], 32, 32, RGBA8_BYTES);
|
||||
add_texture(Distortion_texture[1], 32, 32, RGBA8_BYTES);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void gr_opengl_scene_texture_begin()
|
||||
{
|
||||
if ( !Scene_texture_initialized ) {
|
||||
|
||||
@@ -57,6 +57,15 @@ void gr_opengl_render_shield_impact(shield_material* material_info,
|
||||
|
||||
void opengl_setup_scene_textures();
|
||||
void opengl_scene_texture_shutdown();
|
||||
|
||||
/**
|
||||
* @brief Gives the bytes that the scene, backbuffer, and distortion textures hold
|
||||
*
|
||||
* The profiler overlay's memory panel adds this value to the "render targets" group. The function
|
||||
* counts only the textures that exist at the moment of the call, thus it gives 0 before
|
||||
* opengl_setup_scene_textures() runs and after opengl_scene_texture_shutdown() runs.
|
||||
*/
|
||||
size_t opengl_get_scene_render_target_bytes();
|
||||
void gr_opengl_scene_texture_begin();
|
||||
void gr_opengl_scene_texture_end();
|
||||
void gr_opengl_copy_effect_texture();
|
||||
|
||||
@@ -72,7 +72,7 @@ bool gr_opengl_openxr_test_capabilities() {
|
||||
#ifdef WIN32
|
||||
bool gr_opengl_openxr_create_session() {
|
||||
auto hdc = static_cast<HDC>(SDL_GetPointerProperty(SDL_GetWindowProperties(os::getSDLMainWindow()),
|
||||
SDL_PROP_WINDOW_WIN32_HWND_POINTER,
|
||||
SDL_PROP_WINDOW_WIN32_HDC_POINTER,
|
||||
nullptr));
|
||||
|
||||
XrGraphicsBindingOpenGLWin32KHR graphicsBinding{
|
||||
|
||||
@@ -1093,6 +1093,53 @@ void opengl_post_process_shutdown_bloom()
|
||||
}
|
||||
}
|
||||
|
||||
size_t opengl_get_postprocessing_render_target_bytes()
|
||||
{
|
||||
// The bytes for each pixel come from the internal formats at the creation sites in this file:
|
||||
// GL_RGBA16F holds 8 bytes and GL_RGBA8 holds 4. Keep these values equal to those formats.
|
||||
constexpr size_t RGBA16F_BYTES = 8;
|
||||
constexpr size_t RGBA8_BYTES = 4;
|
||||
|
||||
size_t bytes = 0;
|
||||
|
||||
// The bloom textures are half the size of the post-processing textures. opengl_setup_bloom_textures()
|
||||
// calls glGenerateMipmap() before it sets GL_TEXTURE_MAX_LEVEL, thus the driver holds the full
|
||||
// mipmap chain, not only MAX_MIP_BLUR_LEVELS levels.
|
||||
for (GLuint bloom_texture : Bloom_textures) {
|
||||
if (bloom_texture == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto width = static_cast<size_t>(std::max(1, Post_texture_width >> 1));
|
||||
auto height = static_cast<size_t>(std::max(1, Post_texture_height >> 1));
|
||||
while (true) {
|
||||
bytes += width * height * RGBA16F_BYTES;
|
||||
if ((width == 1) && (height == 1)) {
|
||||
break;
|
||||
}
|
||||
width = std::max<size_t>(1, width >> 1);
|
||||
height = std::max<size_t>(1, height >> 1);
|
||||
}
|
||||
}
|
||||
|
||||
// The SMAA area and search textures stay out of this total. They hold lookup data of a fixed
|
||||
// small size, thus they are not render targets.
|
||||
const auto post_w = static_cast<size_t>(Post_texture_width);
|
||||
const auto post_h = static_cast<size_t>(Post_texture_height);
|
||||
|
||||
if (Smaa_edges_tex != 0) {
|
||||
bytes += post_w * post_h * RGBA8_BYTES;
|
||||
}
|
||||
if (Smaa_blend_tex != 0) {
|
||||
bytes += post_w * post_h * RGBA8_BYTES;
|
||||
}
|
||||
if (Smaa_output_tex != 0) {
|
||||
bytes += post_w * post_h * RGBA8_BYTES;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void opengl_post_process_init()
|
||||
{
|
||||
// Externally used manager class to expose information about post processing externally
|
||||
|
||||
@@ -8,6 +8,14 @@
|
||||
void opengl_post_process_init();
|
||||
void opengl_post_process_shutdown();
|
||||
|
||||
/**
|
||||
* @brief Gives the bytes that the bloom and SMAA render textures hold
|
||||
*
|
||||
* The profiler overlay's memory panel adds this value to the "render targets" group. The function
|
||||
* counts only the textures that exist at the moment of the call.
|
||||
*/
|
||||
size_t opengl_get_postprocessing_render_target_bytes();
|
||||
|
||||
void gr_opengl_post_process_set_effect(const char *name, int x, const vec3d *rgb);
|
||||
void gr_opengl_post_process_set_defaults();
|
||||
void gr_opengl_post_process_save_zbuffer();
|
||||
|
||||
@@ -178,10 +178,20 @@ static SCP_string opengl_shader_get_header(shader_type type_id, int flags, bool
|
||||
|
||||
#ifndef USE_OPENGL_ES
|
||||
sflags << "#version " << GLSL_version << " core\n";
|
||||
if (GLSL_version < 330) {
|
||||
sflags << "#extension GL_ARB_explicit_attrib_location : enable\n";
|
||||
}
|
||||
#else
|
||||
sflags << "#version " << GLSL_version << " es\n";
|
||||
#endif
|
||||
sflags << "#define OPENGL\n";
|
||||
|
||||
#ifdef USE_OPENGL_ES
|
||||
if (!GLAD_GL_EXT_clip_cull_distance) {
|
||||
sflags << "#define SDR_FLAG_SW_CLIP_FALLBACK\n";
|
||||
}
|
||||
#endif
|
||||
|
||||
sflags << shader_get_shadow_cascade_defines();
|
||||
sflags << shader_get_rt_shadow_light_limit_define();
|
||||
|
||||
|
||||
@@ -360,6 +360,13 @@ GLboolean opengl_state::ClipDistance(GLint num, bool state)
|
||||
|
||||
GLboolean save_state = clipdistance_Status[num];
|
||||
|
||||
#ifdef USE_OPENGL_ES
|
||||
if (!GLAD_GL_EXT_clip_cull_distance) {
|
||||
clipdistance_Status[num] = state;
|
||||
return save_state;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (state != clipdistance_Status[num]) {
|
||||
if (state) {
|
||||
glEnable(GL_CLIP_DISTANCE0+num);
|
||||
|
||||
@@ -15,11 +15,14 @@
|
||||
#include "gropenglstate.h"
|
||||
#include "gropengltexture.h"
|
||||
#include "gropengldraw.h"
|
||||
#include "gropenglpostprocessing.h"
|
||||
#include "gropengltnl.h"
|
||||
#include "bmpman/bm_internal.h"
|
||||
#include "bmpman/bmpman.h"
|
||||
#include "cmdline/cmdline.h"
|
||||
#include "ddsutils/ddsutils.h"
|
||||
#include "globalincs/systemvars.h"
|
||||
#include "graphics/2d.h"
|
||||
#include "graphics/grinternal.h"
|
||||
#include "math/vecmat.h"
|
||||
#include "options/Option.h"
|
||||
@@ -146,6 +149,27 @@ static auto AnisotropyOption = options::OptionBuilder<float>("Graphics.Anisotrop
|
||||
int opengl_free_texture(tcache_slot_opengl *t);
|
||||
int opengl_create_texture (int bitmap_handle, int bitmap_type, tcache_slot_opengl *tslot = NULL);
|
||||
|
||||
// Running total of the bytes of texture data held by all live texture cache slots, for the
|
||||
// profiler overlay's memory panel. Kept in sync at every site that fills or drops a slot's data
|
||||
// rather than summed on demand, because the slots live in bmpman's blocks and a full walk of them
|
||||
// each frame is too expensive. Animations put all their frames in one texture array but give each
|
||||
// frame its own slot, so freeing some frames of an animation lowers this total while the array
|
||||
// itself stays alive until the last frame goes away.
|
||||
static size_t GL_texture_bytes_used = 0;
|
||||
|
||||
/**
|
||||
* @brief Removes a texture cache slot's bytes from GL_texture_bytes_used
|
||||
*
|
||||
* Call this at each site that drops the texture data of a slot, immediately before the code that
|
||||
* clears the slot. The function sets the size of the slot to 0, thus a second call for the same
|
||||
* slot does nothing.
|
||||
*/
|
||||
static void opengl_texture_release_bytes(tcache_slot_opengl* t)
|
||||
{
|
||||
GL_texture_bytes_used -= static_cast<size_t>(t->size);
|
||||
t->size = 0;
|
||||
}
|
||||
|
||||
extern int get_num_mipmap_levels(int w, int h);
|
||||
|
||||
void opengl_set_additive_tex_env()
|
||||
@@ -236,6 +260,14 @@ void opengl_tcache_flush()
|
||||
opengl_free_texture(static_cast<tcache_slot_opengl*>(slot.gr_info));
|
||||
}
|
||||
}
|
||||
|
||||
// A flush releases the texture of every slot, thus the total must go to 0. A value that is not
|
||||
// 0 shows that a site which drops texture data does not call opengl_texture_release_bytes().
|
||||
// The shutdown sequence is the one exception: bm_close() removes the blocks before it closes
|
||||
// the graphics backend, thus the loop above then finds no slot to release.
|
||||
Assertion(bm_blocks.empty() || (GL_texture_bytes_used == 0),
|
||||
"OpenGL texture memory tracking lost " SIZE_T_ARG " bytes. A texture release site is missing.",
|
||||
GL_texture_bytes_used);
|
||||
}
|
||||
|
||||
extern void opengl_kill_all_render_targets();
|
||||
@@ -334,6 +366,7 @@ int opengl_free_texture(tcache_slot_opengl *t)
|
||||
// There are still used frames left in the animation so we can't free the texture object here
|
||||
|
||||
// We still need to reset this slot though since it has been "freed"
|
||||
opengl_texture_release_bytes(t);
|
||||
t->reset();
|
||||
|
||||
// Everything is fine
|
||||
@@ -353,6 +386,7 @@ int opengl_free_texture(tcache_slot_opengl *t)
|
||||
glDeleteTextures (1, &t->texture_id);
|
||||
|
||||
// Finally, reset the data of this slot
|
||||
opengl_texture_release_bytes(t);
|
||||
t->reset();
|
||||
|
||||
return 1;
|
||||
@@ -731,10 +765,14 @@ static int opengl_texture_set_level(int bitmap_handle, int bitmap_type, int bmap
|
||||
} // end switch
|
||||
|
||||
tSlot->bitmap_handle = bitmap_handle;
|
||||
// Remove the previous bytes of the slot before the new size replaces them. This function also
|
||||
// runs for a texture that is filled again (a streaming animation, for example).
|
||||
opengl_texture_release_bytes(tSlot);
|
||||
tSlot->size = (dsize) ? ((doffset + dsize) - skip_size) : (tex_w * tex_h * byte_mult);
|
||||
tSlot->w = (ushort)tex_w;
|
||||
tSlot->h = (ushort)tex_h;
|
||||
|
||||
GL_texture_bytes_used += static_cast<size_t>(tSlot->size);
|
||||
GL_textures_in_frame += tSlot->size;
|
||||
|
||||
GL_CHECK_FOR_ERRORS("end of create_texture_sub()");
|
||||
@@ -964,6 +1002,7 @@ int opengl_create_texture(int bitmap_handle, int bitmap_type, tcache_slot_opengl
|
||||
GL_state.Texture.Delete(start_slot->texture_id);
|
||||
glDeleteTextures(1, &start_slot->texture_id);
|
||||
|
||||
opengl_texture_release_bytes(start_slot);
|
||||
start_slot->reset();
|
||||
}
|
||||
}
|
||||
@@ -1034,6 +1073,7 @@ int opengl_create_texture(int bitmap_handle, int bitmap_type, tcache_slot_opengl
|
||||
|
||||
if (tslot->texture_id == 0) {
|
||||
mprintf(("!!OpenGL DEBUG!! t->texture_id == 0\n"));
|
||||
opengl_texture_release_bytes(tslot);
|
||||
tslot->reset();
|
||||
return 0;
|
||||
}
|
||||
@@ -1110,7 +1150,13 @@ int opengl_create_texture(int bitmap_handle, int bitmap_type, tcache_slot_opengl
|
||||
auto index = frame - animation_begin;
|
||||
|
||||
auto frame_slot = bm_get_gr_info<tcache_slot_opengl>(frame, true);
|
||||
// The copy below overwrites the size of the slot, thus the old bytes must go away first
|
||||
opengl_texture_release_bytes(frame_slot);
|
||||
*frame_slot = *tslot; // Copy the existing properties to the slot of this texture
|
||||
// The copy also brought in the size of tslot, which belongs to a different slot and is
|
||||
// already part of GL_texture_bytes_used. opengl_texture_set_level() below gives this slot
|
||||
// its own size.
|
||||
frame_slot->size = 0;
|
||||
frame_slot->array_index = (uint32_t) index;
|
||||
|
||||
// set the bits per pixel
|
||||
@@ -1641,6 +1687,7 @@ struct fbo_t {
|
||||
// these first vars should only be modified in opengl_make_render_target()
|
||||
GLuint renderbuffer_id = 0;
|
||||
GLuint framebuffer_id = 0;
|
||||
size_t renderbuffer_bytes = 0; // for the profiler overlay's memory panel; see GL_renderbuffer_bytes_used
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
// these next 2 should only be modifed in opengl_set_render_target()
|
||||
@@ -1653,6 +1700,12 @@ static SCP_vector<fbo_t> RenderTarget;
|
||||
static fbo_t *render_target = NULL;
|
||||
static int next_fbo_id = 0;
|
||||
|
||||
// Running total of bytes used by depth/stencil renderbuffers across all live FBOs, for the
|
||||
// profiler overlay's memory panel. Kept in sync with each fbo_t's renderbuffer_bytes at every
|
||||
// create/delete site below rather than summed on demand, since RenderTarget entries are reused
|
||||
// (opengl_get_free_fbo) and reset in place.
|
||||
static size_t GL_renderbuffer_bytes_used = 0;
|
||||
|
||||
static fbo_t* opengl_get_fbo(int id) {
|
||||
if (id < 0) {
|
||||
return nullptr;
|
||||
@@ -1768,11 +1821,31 @@ void opengl_kill_render_target(bitmap_slot* slot)
|
||||
if (fbo->renderbuffer_id) {
|
||||
glDeleteRenderbuffers(1, &fbo->renderbuffer_id);
|
||||
fbo->renderbuffer_id = 0;
|
||||
GL_renderbuffer_bytes_used -= fbo->renderbuffer_bytes;
|
||||
fbo->renderbuffer_bytes = 0;
|
||||
}
|
||||
|
||||
opengl_free_fbo_slot(fbo->fbo_id);
|
||||
}
|
||||
|
||||
void gr_opengl_get_memory_stats(gr_memory_stats& stats)
|
||||
{
|
||||
stats.gpu_purpose_valid = true;
|
||||
|
||||
// The textures of the bitmap cache. Render targets that the game creates through bmpman are
|
||||
// part of this group, which matches the Vulkan backend: its bm_make_render_target() images
|
||||
// also go into the texture group.
|
||||
stats.gpu_texture_bytes = GL_texture_bytes_used;
|
||||
|
||||
stats.gpu_geometry_bytes = opengl_get_geometry_bytes();
|
||||
|
||||
// The render target group holds the resources of the backend itself: the depth/stencil
|
||||
// renderbuffers of the bitmap render targets, the scene and backbuffer textures, the
|
||||
// post-processing textures, and the shadow map.
|
||||
stats.gpu_render_target_bytes = GL_renderbuffer_bytes_used + opengl_get_scene_render_target_bytes() +
|
||||
opengl_get_postprocessing_render_target_bytes() + opengl_get_shadow_map_bytes();
|
||||
}
|
||||
|
||||
void opengl_kill_all_render_targets()
|
||||
{
|
||||
for (size_t i = 0; i < RenderTarget.size(); i++) {
|
||||
@@ -1786,10 +1859,18 @@ void opengl_kill_all_render_targets()
|
||||
if (fbo->renderbuffer_id) {
|
||||
glDeleteRenderbuffers(1, &fbo->renderbuffer_id);
|
||||
fbo->renderbuffer_id = 0;
|
||||
GL_renderbuffer_bytes_used -= fbo->renderbuffer_bytes;
|
||||
fbo->renderbuffer_bytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
RenderTarget.clear();
|
||||
|
||||
// Every renderbuffer is gone now, thus the total must go to 0. A value that is not 0 shows
|
||||
// that a site which deletes a renderbuffer does not correct GL_renderbuffer_bytes_used.
|
||||
Assertion(GL_renderbuffer_bytes_used == 0,
|
||||
"OpenGL renderbuffer memory tracking lost " SIZE_T_ARG " bytes. A renderbuffer delete site is missing.",
|
||||
GL_renderbuffer_bytes_used);
|
||||
}
|
||||
|
||||
int opengl_set_render_target( int slot, int face, int is_static )
|
||||
@@ -1946,6 +2027,26 @@ int opengl_make_render_target( int handle, int *w, int *h, int *bpp, int *mm_lvl
|
||||
|
||||
glTexParameteri(GL_texture_target, GL_TEXTURE_MAX_LEVEL, ts->mipmap_levels - 1);
|
||||
|
||||
// Record the bytes of the color texture, which opengl_tex_array_storage() gave the format
|
||||
// GL_RGBA8 (4 bytes for each pixel). A cube map holds 6 faces of that size.
|
||||
size_t rt_texture_bytes = 0;
|
||||
for (int level = 0; level < ts->mipmap_levels; ++level) {
|
||||
auto level_w = static_cast<size_t>(std::max(1, *w >> level));
|
||||
auto level_h = static_cast<size_t>(std::max(1, *h >> level));
|
||||
rt_texture_bytes += level_w * level_h * 4;
|
||||
}
|
||||
if (flags & BMP_FLAG_CUBEMAP) {
|
||||
rt_texture_bytes *= 6;
|
||||
}
|
||||
// A slot that held a render target before must lose its old bytes first.
|
||||
opengl_texture_release_bytes(ts);
|
||||
|
||||
// The size field of the slot is an int, thus the value must stay inside its range. The counter
|
||||
// takes the same (clamped) value, which keeps the add here and the subtract in
|
||||
// opengl_texture_release_bytes() equal.
|
||||
ts->size = static_cast<int>(std::min(rt_texture_bytes, static_cast<size_t>(INT_MAX)));
|
||||
GL_texture_bytes_used += static_cast<size_t>(ts->size);
|
||||
|
||||
GL_state.Texture.Enable(0);
|
||||
|
||||
// render buffer (depth)
|
||||
@@ -1954,6 +2055,10 @@ int opengl_make_render_target( int handle, int *w, int *h, int *bpp, int *mm_lvl
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, new_fbo->renderbuffer_id);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, *w, *h);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
|
||||
// GL_DEPTH24_STENCIL8 is 4 bytes/pixel
|
||||
new_fbo->renderbuffer_bytes = static_cast<size_t>(*w) * static_cast<size_t>(*h) * 4;
|
||||
GL_renderbuffer_bytes_used += new_fbo->renderbuffer_bytes;
|
||||
}
|
||||
|
||||
// frame buffer
|
||||
@@ -1980,6 +2085,7 @@ int opengl_make_render_target( int handle, int *w, int *h, int *bpp, int *mm_lvl
|
||||
|
||||
GL_state.Texture.Delete(ts->texture_id);
|
||||
glDeleteTextures(1, &ts->texture_id);
|
||||
opengl_texture_release_bytes(ts);
|
||||
ts->reset();
|
||||
|
||||
glDeleteFramebuffers(1, &new_fbo->framebuffer_id);
|
||||
@@ -1987,6 +2093,8 @@ int opengl_make_render_target( int handle, int *w, int *h, int *bpp, int *mm_lvl
|
||||
if (new_fbo->renderbuffer_id) {
|
||||
glDeleteRenderbuffers(1, &new_fbo->renderbuffer_id);
|
||||
new_fbo->renderbuffer_id = 0;
|
||||
GL_renderbuffer_bytes_used -= new_fbo->renderbuffer_bytes;
|
||||
new_fbo->renderbuffer_bytes = 0;
|
||||
}
|
||||
|
||||
opengl_set_texture_target();
|
||||
|
||||
@@ -81,6 +81,8 @@ void opengl_set_additive_tex_env();
|
||||
void opengl_set_modulate_tex_env();
|
||||
void opengl_preload_init();
|
||||
void opengl_kill_render_target(bitmap_slot* slot);
|
||||
struct gr_memory_stats;
|
||||
void gr_opengl_get_memory_stats(gr_memory_stats& stats);
|
||||
int opengl_make_render_target(int handle, int *w, int *h, int *bpp, int *mm_lvl, int flags);
|
||||
int opengl_set_render_target(int slot, int face = -1, int is_static = 0);
|
||||
ubyte* gr_opengl_get_bitmap_from_texture(int bitmap_num, int* width_out, int* height_out);
|
||||
|
||||
@@ -59,7 +59,21 @@ extern vec3d G3_user_clip_point;
|
||||
extern bool Envmap_override;
|
||||
extern bool Shadow_override;
|
||||
|
||||
size_t GL_vertex_data_in = 0;
|
||||
// Running total of the bytes held by live vertex and index buffers, for the profiler overlay's
|
||||
// memory panel (see opengl_get_geometry_bytes()). Uniform buffers and texture buffers stay out of
|
||||
// this total, which keeps it equal to the "geometry" group of the Vulkan backend.
|
||||
static size_t GL_geometry_bytes_used = 0;
|
||||
|
||||
// Tells if a buffer of this OpenGL type counts as geometry for GL_geometry_bytes_used.
|
||||
static bool opengl_buffer_is_geometry(GLenum type)
|
||||
{
|
||||
return (type == GL_ARRAY_BUFFER) || (type == GL_ELEMENT_ARRAY_BUFFER);
|
||||
}
|
||||
|
||||
size_t opengl_get_geometry_bytes()
|
||||
{
|
||||
return GL_geometry_bytes_used;
|
||||
}
|
||||
|
||||
GLint GL_max_elements_vertices = 4096;
|
||||
GLint GL_max_elements_indices = 4096;
|
||||
@@ -68,6 +82,11 @@ GLuint Shadow_map_depth_texture = 0;
|
||||
GLuint shadow_fbo = 0;
|
||||
int Shadow_texture_size = 0;
|
||||
|
||||
// Number of layers in the shadow map texture array, kept from the moment
|
||||
// opengl_init_shadow_framebuffer() created the array. opengl_get_shadow_map_bytes() uses it, thus
|
||||
// a later change of the cascade counts cannot make that value wrong.
|
||||
static int Shadow_texture_layers = 0;
|
||||
|
||||
gr_buffer_handle Transform_buffer_handle;
|
||||
|
||||
SCP_unordered_map<vertex_layout, GLuint> Stored_vertex_arrays;
|
||||
@@ -275,9 +294,11 @@ void gr_opengl_update_buffer_data(gr_buffer_handle handle, size_t size, const vo
|
||||
glBufferData(buffer_obj.type, size, data, buffer_obj.gl_usage);
|
||||
}
|
||||
|
||||
GL_vertex_data_in -= buffer_obj.size;
|
||||
if (opengl_buffer_is_geometry(buffer_obj.type)) {
|
||||
GL_geometry_bytes_used -= buffer_obj.size;
|
||||
GL_geometry_bytes_used += size;
|
||||
}
|
||||
buffer_obj.size = size;
|
||||
GL_vertex_data_in += buffer_obj.size;
|
||||
}
|
||||
|
||||
void gr_opengl_update_buffer_data_offset(gr_buffer_handle handle, size_t offset, size_t size, const void* data)
|
||||
@@ -365,7 +386,12 @@ void gr_opengl_delete_buffer(gr_buffer_handle handle)
|
||||
glDeleteTextures(1, &buffer_obj.texture);
|
||||
}
|
||||
|
||||
GL_vertex_data_in -= buffer_obj.size;
|
||||
if (opengl_buffer_is_geometry(buffer_obj.type)) {
|
||||
GL_geometry_bytes_used -= buffer_obj.size;
|
||||
}
|
||||
// The size must go to 0 here. opengl_destroy_all_buffers() calls this function for every
|
||||
// handle, thus a buffer that the game already deleted comes through a second time.
|
||||
buffer_obj.size = 0;
|
||||
|
||||
glDeleteBuffers(1, &buffer_obj.buffer_id);
|
||||
}
|
||||
@@ -487,6 +513,7 @@ static bool opengl_init_shadow_framebuffer(int size)
|
||||
// Everything is fine
|
||||
mprintf(("Shadow framebuffer created successfully.\n"));
|
||||
Shadow_texture_size = size;
|
||||
Shadow_texture_layers = Num_shadow_cascades + Num_cockpit_shadow_cascades;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -517,6 +544,22 @@ static bool opengl_init_shadow_framebuffer(int size)
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t opengl_get_shadow_map_bytes()
|
||||
{
|
||||
if (Shadow_map_depth_texture == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// opengl_init_shadow_framebuffer() gives the texture array the internal format
|
||||
// GL_DEPTH_COMPONENT24, which the driver holds in 4 bytes for each texel.
|
||||
constexpr size_t DEPTH_COMPONENT24_BYTES = 4;
|
||||
|
||||
const auto size = static_cast<size_t>(Shadow_texture_size);
|
||||
const auto layers = static_cast<size_t>(Shadow_texture_layers);
|
||||
|
||||
return size * size * layers * DEPTH_COMPONENT24_BYTES;
|
||||
}
|
||||
|
||||
void opengl_tnl_init()
|
||||
{
|
||||
Transform_buffer_handle = opengl_create_texture_buffer_object();
|
||||
@@ -732,7 +775,13 @@ void gr_opengl_shadow_map_start(matrix4 *shadow_view_matrix, const matrix *light
|
||||
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
#ifdef USE_OPENGL_ES
|
||||
if (GLAD_GL_EXT_depth_clamp) {
|
||||
glEnable(GL_DEPTH_CLAMP);
|
||||
}
|
||||
#else
|
||||
glEnable(GL_DEPTH_CLAMP);
|
||||
#endif
|
||||
|
||||
Glowpoint_override_save = Glowpoint_override;
|
||||
Glowpoint_override = true;
|
||||
@@ -753,7 +802,13 @@ void gr_opengl_shadow_map_end()
|
||||
{
|
||||
gr_end_view_matrix();
|
||||
|
||||
#ifdef USE_OPENGL_ES
|
||||
if (GLAD_GL_EXT_depth_clamp) {
|
||||
glDisable(GL_DEPTH_CLAMP);
|
||||
}
|
||||
#else
|
||||
glDisable(GL_DEPTH_CLAMP);
|
||||
#endif
|
||||
|
||||
gr_zbuffer_set(ZBUFFER_TYPE_FULL);
|
||||
GL_state.PopFramebufferState();
|
||||
@@ -1136,6 +1191,12 @@ void opengl_tnl_set_material_nanovg(nanovg_material* material_info) {
|
||||
void opengl_tnl_set_material_decal(decal_material* material_info) {
|
||||
opengl_tnl_set_material(material_info, false);
|
||||
|
||||
#ifdef USE_OPENGL_ES
|
||||
// ES decal outputs use G-buffer locations instead of packed draw-buffer indices.
|
||||
GL_state.SetAlphaBlendModei(2, material_info->get_blend_mode(1));
|
||||
GL_state.SetAlphaBlendModei(4, material_info->get_blend_mode(2));
|
||||
#endif
|
||||
|
||||
float u_scale, v_scale;
|
||||
uint32_t array_index;
|
||||
|
||||
|
||||
@@ -53,6 +53,22 @@ void gr_opengl_bind_uniform_buffer(uniform_block_type bind_point, size_t offset,
|
||||
void* gr_opengl_map_buffer(gr_buffer_handle handle);
|
||||
void gr_opengl_flush_mapped_buffer(gr_buffer_handle handle, size_t offset, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Gives the bytes that all live vertex and index buffers hold
|
||||
*
|
||||
* The profiler overlay's memory panel shows this value as the "geometry" group. Uniform buffers
|
||||
* and texture buffers are not part of it.
|
||||
*/
|
||||
size_t opengl_get_geometry_bytes();
|
||||
|
||||
/**
|
||||
* @brief Gives the bytes that the shadow map texture array holds
|
||||
*
|
||||
* The profiler overlay's memory panel adds this value to the "render targets" group. The function
|
||||
* gives 0 when shadows are off.
|
||||
*/
|
||||
size_t opengl_get_shadow_map_bytes();
|
||||
|
||||
/**
|
||||
* @brief Retrieves the OpenGL handle of a generic buffer handle
|
||||
* @param expected_type The expected type of the buffer. Mainly used for debug checking.
|
||||
|
||||
@@ -162,6 +162,19 @@ static ShaderVariantInfo SHADER_VARIANTS[] = {
|
||||
{SDR_TYPE_SHADOW_MAP_GEN, true, SDR_FLAG_SHADOW_FALLBACK, "GEOMETRY_FALLBACK", {}, "Use a geometry shader to select the cascade layer instead of writing gl_Layer from the vertex shader"}
|
||||
};
|
||||
|
||||
static_assert(VATTRIB_MODEL_MATRIX + 4 <= 32, "vertex input mask is 32 bits wide");
|
||||
|
||||
uint32_t shader_vertex_input_mask(const ShaderTypeInfo& info)
|
||||
{
|
||||
uint32_t mask = 0;
|
||||
for (auto attr : info.attributes) {
|
||||
// The Vulkan pipeline drops every location the mask omits, so a mat4 needs all four of its bits.
|
||||
const uint32_t locations = (attr == VATTRIB_MODEL_MATRIX) ? 4u : 1u;
|
||||
mask |= ((1u << locations) - 1u) << attr;
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
const ShaderTypeInfo* shader_get_type_info(shader_type type)
|
||||
{
|
||||
for (auto & i : SHADER_TYPES) {
|
||||
|
||||
@@ -55,6 +55,14 @@ const ShaderTypeInfo* shader_get_type_info(shader_type type);
|
||||
// Lookup helper: find variant info by (type, flag). Returns nullptr if not found.
|
||||
const ShaderVariantInfo* shader_get_variant_info(shader_type type, int flag);
|
||||
|
||||
/**
|
||||
* @brief Bitmask of the vertex attribute locations a shader type reads.
|
||||
*
|
||||
* One bit per location; VATTRIB_MODEL_MATRIX contributes bits 8-11 because a
|
||||
* mat4 input occupies four consecutive locations.
|
||||
*/
|
||||
uint32_t shader_vertex_input_mask(const ShaderTypeInfo& info);
|
||||
|
||||
// Iterate all variants matching type whose flag bit is set in flags.
|
||||
void shader_for_each_active_variant(shader_type type, unsigned int flags, const std::function<void(const ShaderVariantInfo&)>& fn);
|
||||
|
||||
|
||||
@@ -70,6 +70,12 @@ void GPUMemoryHeap::freeGpuData(size_t offset) {
|
||||
gr_buffer_handle GPUMemoryHeap::bufferHandle() {
|
||||
return _bufferHandle;
|
||||
}
|
||||
size_t GPUMemoryHeap::usedBytes() const {
|
||||
return _allocator->usedBytes();
|
||||
}
|
||||
size_t GPUMemoryHeap::bufferSize() const {
|
||||
return _bufferSize;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,18 @@ class GPUMemoryHeap {
|
||||
* @return The graphics code buffer handle.
|
||||
*/
|
||||
gr_buffer_handle bufferHandle();
|
||||
|
||||
/**
|
||||
* @brief Gets the number of bytes currently allocated from this heap
|
||||
* @return The sum of the sizes of all active allocations.
|
||||
*/
|
||||
size_t usedBytes() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the total size of the backing GPU buffer, including both allocated and free space
|
||||
* @return The current buffer size.
|
||||
*/
|
||||
size_t bufferSize() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -339,7 +339,22 @@ vk::BufferUsageFlags VulkanBufferManager::getVkUsageFlags(BufferType type, bool
|
||||
return flags;
|
||||
}
|
||||
|
||||
MemoryUsage VulkanBufferManager::getMemoryUsage(BufferUsageHint hint)
|
||||
namespace {
|
||||
MemoryPurpose bufferTypeToPurpose(BufferType type)
|
||||
{
|
||||
switch (type) {
|
||||
case BufferType::Vertex:
|
||||
case BufferType::Index:
|
||||
return MemoryPurpose::Geometry;
|
||||
case BufferType::Uniform:
|
||||
default:
|
||||
// Uniform buffers are already tracked separately via UniformBufferManager/gr_debug_stats.
|
||||
return MemoryPurpose::Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MemoryUsage VulkanBufferManager::getMemoryUsage(BufferUsageHint hint)
|
||||
{
|
||||
switch (hint) {
|
||||
case BufferUsageHint::Static:
|
||||
@@ -455,7 +470,8 @@ bool VulkanBufferManager::createOrResizeBuffer(VulkanBufferObject& bufferObj, si
|
||||
|
||||
// Allocate memory
|
||||
MemoryUsage memUsage = getMemoryUsage(bufferObj.usage);
|
||||
if (!m_memoryManager->allocateBufferMemory(bufferObj.buffer, memUsage, bufferObj.allocation)) {
|
||||
if (!m_memoryManager->allocateBufferMemory(
|
||||
bufferObj.buffer, memUsage, bufferObj.allocation, bufferTypeToPurpose(bufferObj.type))) {
|
||||
m_device.destroyBuffer(bufferObj.buffer);
|
||||
bufferObj.buffer = oldBuffer;
|
||||
bufferObj.allocation = oldAllocation;
|
||||
|
||||
@@ -132,6 +132,20 @@ vk::PrimitiveTopology convertPrimitiveType(primitive_type type)
|
||||
}
|
||||
}
|
||||
|
||||
bool topologySupportsPrimitiveRestart(vk::PrimitiveTopology topology)
|
||||
{
|
||||
switch (topology) {
|
||||
case vk::PrimitiveTopology::eLineStrip:
|
||||
case vk::PrimitiveTopology::eTriangleStrip:
|
||||
case vk::PrimitiveTopology::eTriangleFan:
|
||||
case vk::PrimitiveTopology::eLineStripWithAdjacency:
|
||||
case vk::PrimitiveTopology::eTriangleStripWithAdjacency:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
vk::CullModeFlags convertCullMode(bool cullEnabled)
|
||||
{
|
||||
return cullEnabled ? vk::CullModeFlagBits::eBack : vk::CullModeFlagBits::eNone;
|
||||
|
||||
@@ -46,6 +46,15 @@ vk::StencilOp convertStencilOp(StencilOperation op);
|
||||
*/
|
||||
vk::PrimitiveTopology convertPrimitiveType(primitive_type type);
|
||||
|
||||
/**
|
||||
* @brief Whether a topology may declare primitiveRestartEnable
|
||||
*
|
||||
* True for the strip/fan topologies. For list topologies the Vulkan spec requires
|
||||
* primitiveRestartEnable to be VK_FALSE unless VK_EXT_primitive_topology_list_restart is
|
||||
* enabled, which FSO does not request.
|
||||
*/
|
||||
bool topologySupportsPrimitiveRestart(vk::PrimitiveTopology topology);
|
||||
|
||||
/**
|
||||
* @brief Convert FSO cull mode to Vulkan cull mode
|
||||
* @param cullEnabled Whether culling is enabled
|
||||
|
||||
@@ -374,13 +374,13 @@ void vulkan_deferred_lighting_msaa()
|
||||
// Global set (fallback — resolve shader doesn't use global bindings)
|
||||
vk::DescriptorSet globalSet = descriptorMgr->allocateFrameSet(DescriptorSetIndex::Global);
|
||||
Assert(globalSet);
|
||||
writer.writeSet(globalSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::Global));
|
||||
writer.writeSet(DescriptorSetIndex::Global, globalSet);
|
||||
|
||||
// Material set: All 6 MSAA textures in binding 1 array (elements 0-5)
|
||||
// [0]=color, [1]=position, [2]=normal, [3]=specular, [4]=emissive, [5]=depth
|
||||
vk::DescriptorSet materialSet = descriptorMgr->allocateFrameSet(DescriptorSetIndex::Material);
|
||||
Assert(materialSet);
|
||||
writer.writeSet(materialSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::Material));
|
||||
writer.writeSet(DescriptorSetIndex::Material, materialSet);
|
||||
|
||||
// Build texture array: elements 0-5 are MSAA textures, 6-15 are fallback
|
||||
vk::Sampler nearestSampler = texMgr->getSampler(
|
||||
@@ -402,7 +402,7 @@ void vulkan_deferred_lighting_msaa()
|
||||
// PerDraw set: GenericData UBO with {samples, fov} at binding 0
|
||||
vk::DescriptorSet perDrawSet = descriptorMgr->allocateFrameSet(DescriptorSetIndex::PerDraw);
|
||||
Assert(perDrawSet);
|
||||
writer.writeSet(perDrawSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::PerDraw));
|
||||
writer.writeSet(DescriptorSetIndex::PerDraw, perDrawSet);
|
||||
|
||||
struct MsaaResolveData {
|
||||
int samples;
|
||||
@@ -416,15 +416,8 @@ void vulkan_deferred_lighting_msaa()
|
||||
ring.alloc(descriptorMgr->getCurrentFrame(), &resolveData, sizeof(resolveData));
|
||||
writer.setBuffer(PerDrawBinding::GenericData, {ring.buffer(), slotOffset, ring.slotSize()});
|
||||
writer.flush();
|
||||
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||
pipelineMgr->getPipelineLayout(),
|
||||
static_cast<uint32_t>(DescriptorSetIndex::Global), globalSet, {});
|
||||
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||
pipelineMgr->getPipelineLayout(),
|
||||
static_cast<uint32_t>(DescriptorSetIndex::Material), materialSet, {});
|
||||
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||
pipelineMgr->getPipelineLayout(),
|
||||
static_cast<uint32_t>(DescriptorSetIndex::PerDraw), perDrawSet, {});
|
||||
const vk::DescriptorSet sets[] = {globalSet, materialSet, perDrawSet};
|
||||
writer.bindSets(cmd, pipelineMgr->getPipelineLayout(), DescriptorSetIndex::Global, sets);
|
||||
|
||||
cmd.draw(3, 1, 0, 0);
|
||||
}
|
||||
@@ -959,6 +952,7 @@ void vulkan_render_decals(decal_material* material_info,
|
||||
// Build pipeline config for decal rendering
|
||||
PipelineConfig config;
|
||||
config.shaderType = SDR_TYPE_DECAL;
|
||||
config.shaderFlags = material_info->get_shader_flags();
|
||||
config.primitiveType = prim_type;
|
||||
config.depthMode = material_info->get_depth_mode();
|
||||
config.depthWriteEnabled = false;
|
||||
@@ -1013,12 +1007,12 @@ void vulkan_render_decals(decal_material* material_info,
|
||||
// Set 0: Global (all fallback)
|
||||
vk::DescriptorSet globalSet = descManager->allocateFrameSet(DescriptorSetIndex::Global);
|
||||
Assert(globalSet);
|
||||
writer.writeSet(globalSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::Global));
|
||||
writer.writeSet(DescriptorSetIndex::Global, globalSet);
|
||||
|
||||
// Set 1: Material
|
||||
vk::DescriptorSet materialSet = descManager->allocateFrameSet(DescriptorSetIndex::Material);
|
||||
Assert(materialSet);
|
||||
writer.writeSet(materialSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::Material));
|
||||
writer.writeSet(DescriptorSetIndex::Material, materialSet);
|
||||
|
||||
// Binding 2: DecalGlobals UBO
|
||||
writer.setBuffer(MaterialBinding::DecalGlobals,
|
||||
@@ -1052,15 +1046,19 @@ void vulkan_render_decals(decal_material* material_info,
|
||||
// Set 2: PerDraw
|
||||
vk::DescriptorSet perDrawSet = descManager->allocateFrameSet(DescriptorSetIndex::PerDraw);
|
||||
Assert(perDrawSet);
|
||||
writer.writeSet(perDrawSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::PerDraw));
|
||||
writer.writeSet(DescriptorSetIndex::PerDraw, perDrawSet);
|
||||
writer.setBuffer(PerDrawBinding::Matrices,
|
||||
getPendingBufInfo(static_cast<size_t>(uniform_block_type::Matrices)));
|
||||
writer.setBuffer(PerDrawBinding::DecalInfo,
|
||||
getPendingBufInfo(static_cast<size_t>(uniform_block_type::DecalInfo)));
|
||||
writer.flush();
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::Global, globalSet);
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::Material, materialSet);
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::PerDraw, perDrawSet);
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::Global, globalSet, nullptr);
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::Material,
|
||||
materialSet,
|
||||
writer.dynamicOffsets(DescriptorSetIndex::Material));
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::PerDraw,
|
||||
perDrawSet,
|
||||
writer.dynamicOffsets(DescriptorSetIndex::PerDraw));
|
||||
|
||||
// Bind vertex buffers: binding 0 = box VBO, binding 1 = instance buffer
|
||||
vk::Buffer boxVBO = bufferManager->getVkBuffer(buffers.Vbuffer_handle);
|
||||
|
||||
@@ -19,6 +19,12 @@ static constexpr DescriptorBindingTemplate s_globalBindings[] = {
|
||||
static constexpr DescriptorBindingTemplate s_globalTlasBinding{
|
||||
GlobalBinding::Tlas, vk::DescriptorType::eAccelerationStructureKHR, 1, vk::ShaderStageFlagBits::eFragment};
|
||||
|
||||
// GLOBAL_DYNAMIC_OFFSET_COUNT is stated rather than derived because this set's template is
|
||||
// assembled at runtime (see below); keep the two in agreement. The optional TLAS binding is not
|
||||
// a dynamic UBO, so it cannot change the count either.
|
||||
static_assert(dynamic_offset_count(s_globalBindings) == GLOBAL_DYNAMIC_OFFSET_COUNT,
|
||||
"The Global set gained a dynamic binding; GLOBAL_DYNAMIC_OFFSET_COUNT must follow.");
|
||||
|
||||
// The Global set's layout must omit the TLAS binding entirely on devices without
|
||||
// VK_KHR_acceleration_structure enabled -- a descriptor type the device didn't
|
||||
// enable the extension for is invalid in vkCreateDescriptorSetLayout. So, unlike
|
||||
@@ -27,26 +33,10 @@ static constexpr DescriptorBindingTemplate s_globalTlasBinding{
|
||||
// the per-instance m_globalBindingsRuntime/m_globalTemplateRuntime members
|
||||
// (see VulkanDescriptorManager.h), rather than compiled in as a fixed constexpr array.
|
||||
|
||||
static constexpr DescriptorBindingTemplate s_materialBindings[] = {
|
||||
{MaterialBinding::ModelData, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{MaterialBinding::TextureArray, vk::DescriptorType::eCombinedImageSampler, 16, vk::ShaderStageFlagBits::eFragment, vk::ImageViewType::e2DArray},
|
||||
{MaterialBinding::DecalGlobals, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{MaterialBinding::TransformSSBO, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eVertex},
|
||||
{MaterialBinding::DepthMap, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{MaterialBinding::SceneColor, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{MaterialBinding::DistortionMap, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{MaterialBinding::ShadowMapData, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex},
|
||||
};
|
||||
static constexpr DescriptorSetTemplate s_materialTemplate(s_materialBindings);
|
||||
|
||||
static constexpr DescriptorBindingTemplate s_perDrawBindings[] = {
|
||||
{PerDrawBinding::GenericData, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{PerDrawBinding::Matrices, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{PerDrawBinding::NanoVGData, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{PerDrawBinding::DecalInfo, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{PerDrawBinding::MovieData, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
};
|
||||
static constexpr DescriptorSetTemplate s_perDrawTemplate(s_perDrawBindings);
|
||||
// The binding declarations themselves live in the header (MaterialSetBindings /
|
||||
// PerDrawSetBindings) because the dynamic-offset layout is derived from them there.
|
||||
static constexpr DescriptorSetTemplate s_materialTemplate(MaterialSetBindings);
|
||||
static constexpr DescriptorSetTemplate s_perDrawTemplate(PerDrawSetBindings);
|
||||
|
||||
// ========== Static uniform binding mappings ==========
|
||||
|
||||
@@ -92,12 +82,21 @@ const vk::DescriptorImageInfo& DescriptorFallbacks::getImage(vk::ImageViewType t
|
||||
|
||||
// ========== DescriptorWriter template-based methods ==========
|
||||
|
||||
void DescriptorWriter::writeSet(vk::DescriptorSet set, const DescriptorSetTemplate& tmpl)
|
||||
void DescriptorWriter::writeSet(DescriptorSetIndex setIndex, vk::DescriptorSet set)
|
||||
{
|
||||
Assert(m_fallbacks);
|
||||
|
||||
const DescriptorSetTemplate& tmpl = VulkanDescriptorManager::getSetTemplate(setIndex);
|
||||
|
||||
// Clear binding slots for this set
|
||||
m_bindingSlots = {};
|
||||
m_currentSet = setIndex;
|
||||
|
||||
// Dynamic offsets are consumed in binding order, so assign slots as the template is walked
|
||||
// (templates list bindings in ascending binding number). This must stay the same rule
|
||||
// dynamic_offset_slot() applies — hence advancing by b.count, not by one binding.
|
||||
uint32_t nextDynIndex = 0;
|
||||
m_dynOffsets[static_cast<size_t>(setIndex)] = {};
|
||||
|
||||
for (const auto& b : tmpl) {
|
||||
Assert(m_writeCount < MAX_WRITES);
|
||||
@@ -116,6 +115,11 @@ void DescriptorWriter::writeSet(vk::DescriptorSet set, const DescriptorSetTempla
|
||||
|
||||
bool isImage = (b.type == vk::DescriptorType::eCombinedImageSampler);
|
||||
bool isAccelStruct = (b.type == vk::DescriptorType::eAccelerationStructureKHR);
|
||||
if (b.type == vk::DescriptorType::eUniformBufferDynamic) {
|
||||
Assert(nextDynIndex + b.count <= MAX_DYNAMIC_OFFSETS_PER_SET);
|
||||
slot.dynIndex = static_cast<int>(nextDynIndex);
|
||||
nextDynIndex += b.count;
|
||||
}
|
||||
if (isImage) {
|
||||
Assert(m_imageInfoCount + b.count <= MAX_IMAGE_INFOS);
|
||||
auto* dst = &m_imageInfos[m_imageInfoCount];
|
||||
@@ -144,12 +148,40 @@ void DescriptorWriter::writeSet(vk::DescriptorSet set, const DescriptorSetTempla
|
||||
} else {
|
||||
Assert(m_bufferInfoCount < MAX_BUFFER_INFOS);
|
||||
m_bufferInfos[m_bufferInfoCount] = m_fallbacks->buffer;
|
||||
// The fallback buffer info starts at offset 0, which is also the base a
|
||||
// dynamic descriptor needs (its offset lives in the dynamic-offset array,
|
||||
// left at 0 here until setBuffer supplies a real one).
|
||||
w.pBufferInfo = &m_bufferInfos[m_bufferInfoCount];
|
||||
slot.bufferInfo = &m_bufferInfos[m_bufferInfoCount++];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DescriptorWriter::bindSets(vk::CommandBuffer cmd,
|
||||
vk::PipelineLayout layout,
|
||||
DescriptorSetIndex firstSet,
|
||||
ArrayView<vk::DescriptorSet> sets)
|
||||
{
|
||||
size_t out = 0;
|
||||
for (size_t i = 0; i < sets.size; ++i) {
|
||||
const auto set = static_cast<DescriptorSetIndex>(static_cast<uint32_t>(firstSet) + i);
|
||||
Assert(static_cast<uint32_t>(set) < static_cast<uint32_t>(DescriptorSetIndex::Count));
|
||||
|
||||
const uint32_t dynCount = VulkanDescriptorManager::getDynamicOffsetCount(set);
|
||||
Assert(dynCount <= MAX_DYNAMIC_OFFSETS_PER_SET);
|
||||
for (uint32_t j = 0; j < dynCount; ++j) {
|
||||
Assert(out < m_dynOffsetScratch.size());
|
||||
m_dynOffsetScratch[out++] = m_dynOffsets[static_cast<size_t>(set)][j];
|
||||
}
|
||||
}
|
||||
|
||||
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||
layout,
|
||||
static_cast<uint32_t>(firstSet),
|
||||
vk::ArrayProxy<const vk::DescriptorSet>(static_cast<uint32_t>(sets.size), sets.data),
|
||||
vk::ArrayProxy<const uint32_t>(static_cast<uint32_t>(out), m_dynOffsetScratch.data()));
|
||||
}
|
||||
|
||||
void DescriptorWriter::flush()
|
||||
{
|
||||
if (m_writeCount > 0) {
|
||||
@@ -167,6 +199,25 @@ void DescriptorWriter::setBuffer(uint32_t binding, const vk::DescriptorBufferInf
|
||||
Assert(binding < MAX_BINDINGS_PER_SET);
|
||||
auto& slot = m_bindingSlots[binding];
|
||||
Assert(slot.bufferInfo);
|
||||
|
||||
if (slot.dynIndex >= 0) {
|
||||
// Dynamic binding: the descriptor holds only {buffer, 0, range}; the caller's
|
||||
// offset becomes the dynamic offset applied at bind time. Keeping it out of
|
||||
// the descriptor is what lets the set survive an offset-only change.
|
||||
auto& dyn = m_dynOffsets[static_cast<size_t>(m_currentSet)][static_cast<size_t>(slot.dynIndex)];
|
||||
if (info.buffer) {
|
||||
Assertion(info.offset <= static_cast<vk::DeviceSize>(UINT32_MAX),
|
||||
"Dynamic uniform buffer offset " SIZE_T_ARG " does not fit in a uint32 dynamic offset!",
|
||||
static_cast<size_t>(info.offset));
|
||||
*slot.bufferInfo = vk::DescriptorBufferInfo(info.buffer, 0, info.range);
|
||||
dyn = static_cast<uint32_t>(info.offset);
|
||||
} else {
|
||||
*slot.bufferInfo = m_fallbacks->buffer;
|
||||
dyn = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.buffer) {
|
||||
*slot.bufferInfo = info;
|
||||
} else {
|
||||
@@ -279,6 +330,23 @@ const DescriptorSetTemplate& VulkanDescriptorManager::getSetTemplate(DescriptorS
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t VulkanDescriptorManager::getDynamicOffsetCount(DescriptorSetIndex setIndex)
|
||||
{
|
||||
// Constant per set (see the derived *_DYNAMIC_OFFSET_COUNT values), not a scan of the
|
||||
// template — this runs on every descriptor set bind.
|
||||
switch (setIndex) {
|
||||
case DescriptorSetIndex::Global:
|
||||
return GLOBAL_DYNAMIC_OFFSET_COUNT;
|
||||
case DescriptorSetIndex::Material:
|
||||
return MATERIAL_DYNAMIC_OFFSET_COUNT;
|
||||
case DescriptorSetIndex::PerDraw:
|
||||
return PERDRAW_DYNAMIC_OFFSET_COUNT;
|
||||
default:
|
||||
UNREACHABLE("Invalid descriptor set index %u!", static_cast<uint32_t>(setIndex));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
vk::DescriptorSetLayout VulkanDescriptorManager::getSetLayout(DescriptorSetIndex setIndex) const
|
||||
{
|
||||
return m_setLayouts[static_cast<size_t>(setIndex)].get();
|
||||
@@ -294,22 +362,31 @@ vk::DescriptorSet VulkanDescriptorManager::allocateFrameSet(DescriptorSetIndex s
|
||||
auto& pools = m_framePools[m_currentFrame];
|
||||
++m_setsAllocatedThisFrame;
|
||||
|
||||
// Allocate through the non-throwing, non-allocating vulkan.hpp overload: this is
|
||||
// one of the hottest calls in the backend (thousands per frame), and the
|
||||
// vector-returning overload heap-allocates on every single one.
|
||||
vk::DescriptorSetAllocateInfo allocInfo;
|
||||
allocInfo.descriptorSetCount = 1;
|
||||
allocInfo.pSetLayouts = &layout;
|
||||
|
||||
// Try allocating from the last pool in the list
|
||||
if (!pools.empty()) {
|
||||
vk::DescriptorSetAllocateInfo allocInfo;
|
||||
allocInfo.descriptorPool = pools.back().get();
|
||||
allocInfo.descriptorSetCount = 1;
|
||||
allocInfo.pSetLayouts = &layout;
|
||||
|
||||
try {
|
||||
auto sets = m_device.allocateDescriptorSets(allocInfo);
|
||||
return sets[0];
|
||||
} catch (const vk::OutOfPoolMemoryError&) {
|
||||
// Pool exhausted, fall through to create a new one
|
||||
vk::DescriptorSet set;
|
||||
vk::Result result = m_device.allocateDescriptorSets(&allocInfo, &set);
|
||||
if (result == vk::Result::eSuccess) {
|
||||
return set;
|
||||
}
|
||||
if (result == vk::Result::eErrorOutOfPoolMemory) {
|
||||
nprintf(("vulkan", "VulkanDescriptorManager: frame pool exhausted, allocating an additional pool chunk\n"));
|
||||
} catch (const vk::FragmentedPoolError&) {
|
||||
// Pool fragmented, fall through to create a new one
|
||||
} else if (result == vk::Result::eErrorFragmentedPool) {
|
||||
nprintf(("vulkan", "VulkanDescriptorManager: frame pool fragmented, allocating an additional pool chunk\n"));
|
||||
} else {
|
||||
nprintf(("vulkan",
|
||||
"VulkanDescriptorManager: descriptor set allocation failed (%s)\n",
|
||||
vk::to_string(result).c_str()));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,18 +395,17 @@ vk::DescriptorSet VulkanDescriptorManager::allocateFrameSet(DescriptorSetIndex s
|
||||
nprintf(("vulkandescriptor", "VulkanDescriptorManager: Grew frame %u pool count to %zu\n",
|
||||
m_currentFrame, pools.size()));
|
||||
|
||||
vk::DescriptorSetAllocateInfo allocInfo;
|
||||
allocInfo.descriptorPool = pools.back().get();
|
||||
allocInfo.descriptorSetCount = 1;
|
||||
allocInfo.pSetLayouts = &layout;
|
||||
|
||||
try {
|
||||
auto sets = m_device.allocateDescriptorSets(allocInfo);
|
||||
return sets[0];
|
||||
} catch (const vk::SystemError& e) {
|
||||
nprintf(("vulkan", "VulkanDescriptorManager: Failed to allocate frame descriptor set after pool growth: %s\n", e.what()));
|
||||
vk::DescriptorSet set;
|
||||
vk::Result result = m_device.allocateDescriptorSets(&allocInfo, &set);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
nprintf(("vulkan",
|
||||
"VulkanDescriptorManager: Failed to allocate frame descriptor set after pool growth: %s\n",
|
||||
vk::to_string(result).c_str()));
|
||||
return {};
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
void VulkanDescriptorManager::beginFrame()
|
||||
@@ -403,9 +479,10 @@ vk::UniqueDescriptorPool VulkanDescriptorManager::createFramePool()
|
||||
// constants in VulkanDescriptorManager.h. Additional pool chunks are created
|
||||
// automatically when one is exhausted.
|
||||
SCP_vector<vk::DescriptorPoolSize> poolSizes = {
|
||||
{ vk::DescriptorType::eUniformBuffer, MAX_UNIFORM_BUFFERS_PER_POOL },
|
||||
{ vk::DescriptorType::eCombinedImageSampler, MAX_SAMPLERS_PER_POOL },
|
||||
{ vk::DescriptorType::eStorageBuffer, MAX_SETS_PER_POOL },
|
||||
{vk::DescriptorType::eUniformBuffer, MAX_UNIFORM_BUFFERS_PER_POOL},
|
||||
{vk::DescriptorType::eUniformBufferDynamic, MAX_DYNAMIC_UNIFORM_BUFFERS_PER_POOL},
|
||||
{vk::DescriptorType::eCombinedImageSampler, MAX_SAMPLERS_PER_POOL},
|
||||
{vk::DescriptorType::eStorageBuffer, MAX_SETS_PER_POOL},
|
||||
};
|
||||
if (m_raytracingEnabled) {
|
||||
poolSizes.emplace_back(vk::DescriptorType::eAccelerationStructureKHR, MAX_SETS_PER_POOL);
|
||||
|
||||
@@ -52,71 +52,6 @@ struct DescriptorFallbacks {
|
||||
const vk::DescriptorImageInfo& getImage(vk::ImageViewType t) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Stack-allocated batch writer for descriptor set updates.
|
||||
*
|
||||
* Usage: reset() + writeSet() (pre-fills all bindings with fallbacks)
|
||||
* + setBuffer/setImage overrides for real data + flush().
|
||||
*/
|
||||
class DescriptorWriter {
|
||||
public:
|
||||
// Fixed capacities for the stack-allocated staging arrays below — NOT Vulkan
|
||||
// hardware limits. They bound a single vkUpdateDescriptorSets batch (one
|
||||
// writeSet), sized to comfortably cover the largest descriptor set layout the
|
||||
// engine builds. Because the backing arrays live on the stack (see m_writes
|
||||
// etc.), the numbers carry no runtime cost beyond that fixed footprint; the
|
||||
// only failure mode is under-sizing, which is Assert-guarded at fill time in
|
||||
// DescriptorWriter::writeSet (e.g. `Assert(m_writeCount < MAX_WRITES)`) so an
|
||||
// oversized layout trips loudly in debug rather than silently overflowing.
|
||||
// Raise these if a future set layout legitimately needs more bindings/infos.
|
||||
static constexpr uint32_t MAX_WRITES = 32; // distinct bindings written per batch
|
||||
static constexpr uint32_t MAX_BUFFER_INFOS = 20; // buffer descriptors staged per batch
|
||||
static constexpr uint32_t MAX_IMAGE_INFOS = 24; // image/sampler descriptors staged per batch
|
||||
static constexpr uint32_t MAX_ACCEL_STRUCT_INFOS = 2; // TLAS descriptors staged per batch (RT)
|
||||
static constexpr uint32_t MAX_BINDINGS_PER_SET = 16; // highest binding number addressable in a set
|
||||
|
||||
void reset(vk::Device device, const DescriptorFallbacks& fallbacks) {
|
||||
m_device = device;
|
||||
m_fallbacks = &fallbacks;
|
||||
m_writeCount = 0;
|
||||
m_bufferInfoCount = 0;
|
||||
m_imageInfoCount = 0;
|
||||
m_accelStructInfoCount = 0;
|
||||
}
|
||||
|
||||
void writeSet(vk::DescriptorSet set, const DescriptorSetTemplate& tmpl);
|
||||
|
||||
void setBuffer(uint32_t binding, const vk::DescriptorBufferInfo& info);
|
||||
void setImage(uint32_t binding, const vk::DescriptorImageInfo& info);
|
||||
void setImageArray(uint32_t binding, ArrayView<vk::DescriptorImageInfo> infos);
|
||||
|
||||
void flush(); // defined in VulkanDescriptorManager.cpp (reports write stats to the manager)
|
||||
|
||||
private:
|
||||
// Per-binding lookup for the current writeSet, indexed by binding number.
|
||||
// Populated by writeSet, used by setBuffer/setImage/setImageArray for O(1) access.
|
||||
struct BindingSlot {
|
||||
vk::DescriptorBufferInfo* bufferInfo = nullptr; // non-null for buffer bindings
|
||||
vk::DescriptorImageInfo* imageInfo = nullptr; // non-null for image bindings
|
||||
uint32_t count = 0; // descriptor count (1 or 16 for arrays)
|
||||
vk::ImageViewType viewType = vk::ImageViewType::e2D; // for fallback lookup
|
||||
};
|
||||
|
||||
vk::Device m_device;
|
||||
const DescriptorFallbacks* m_fallbacks = nullptr;
|
||||
|
||||
std::array<vk::WriteDescriptorSet, MAX_WRITES> m_writes;
|
||||
std::array<vk::DescriptorBufferInfo, MAX_BUFFER_INFOS> m_bufferInfos;
|
||||
std::array<vk::DescriptorImageInfo, MAX_IMAGE_INFOS> m_imageInfos;
|
||||
std::array<vk::AccelerationStructureKHR, MAX_ACCEL_STRUCT_INFOS> m_accelStructInfos;
|
||||
std::array<vk::WriteDescriptorSetAccelerationStructureKHR, MAX_ACCEL_STRUCT_INFOS> m_asWriteInfos;
|
||||
std::array<BindingSlot, MAX_BINDINGS_PER_SET> m_bindingSlots;
|
||||
uint32_t m_writeCount = 0;
|
||||
uint32_t m_bufferInfoCount = 0;
|
||||
uint32_t m_imageInfoCount = 0;
|
||||
uint32_t m_accelStructInfoCount = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Descriptor set indices for the 3-tier layout
|
||||
*
|
||||
@@ -146,15 +81,18 @@ namespace GlobalBinding {
|
||||
}
|
||||
|
||||
// Material Set (Set 1) bindings — per-material data
|
||||
// ModelData and ShadowMapData are *dynamic* UBOs: their offset moves every draw
|
||||
// while the rest of the set is unchanged, so keeping the offset out of the
|
||||
// descriptor is what makes the set cacheable across draws.
|
||||
namespace MaterialBinding {
|
||||
static constexpr uint32_t ModelData = 0; // UBO: model/material data
|
||||
static constexpr uint32_t TextureArray = 1; // sampler2D[16]: material textures
|
||||
static constexpr uint32_t DecalGlobals = 2; // UBO: decal globals
|
||||
static constexpr uint32_t TransformSSBO = 3; // SSBO: batched transforms
|
||||
static constexpr uint32_t DepthMap = 4; // sampler2D: depth (soft particles)
|
||||
static constexpr uint32_t SceneColor = 5; // sampler2D: scene color (distortion)
|
||||
static constexpr uint32_t DistortionMap = 6; // sampler2D: distortion texture
|
||||
static constexpr uint32_t ShadowMapData = 7; // UBO: shadow map generation per-draw data (shadow_render_list)
|
||||
static constexpr uint32_t ModelData = 0; // dynamic UBO: model/material data
|
||||
static constexpr uint32_t TextureArray = 1; // sampler2D[16]: material textures
|
||||
static constexpr uint32_t DecalGlobals = 2; // UBO: decal globals
|
||||
static constexpr uint32_t TransformSSBO = 3; // SSBO: batched transforms
|
||||
static constexpr uint32_t DepthMap = 4; // sampler2D: depth (soft particles)
|
||||
static constexpr uint32_t SceneColor = 5; // sampler2D: scene color (distortion)
|
||||
static constexpr uint32_t DistortionMap = 6; // sampler2D: distortion texture
|
||||
static constexpr uint32_t ShadowMapData = 7; // dynamic UBO: shadow map generation per-draw data (shadow_render_list)
|
||||
}
|
||||
|
||||
// Texture array slot indices (elements within MaterialBinding::TextureArray)
|
||||
@@ -169,14 +107,256 @@ namespace TextureSlot {
|
||||
}
|
||||
|
||||
// PerDraw Set (Set 2) bindings — per-draw-call data
|
||||
// GenericData and Matrices are *dynamic* UBOs, for the same reason as the
|
||||
// Material set's ModelData.
|
||||
namespace PerDrawBinding {
|
||||
static constexpr uint32_t GenericData = 0; // UBO: generic shader data
|
||||
static constexpr uint32_t Matrices = 1; // UBO: transform matrices
|
||||
static constexpr uint32_t NanoVGData = 2; // UBO: NanoVG data
|
||||
static constexpr uint32_t DecalInfo = 3; // UBO: per-decal info
|
||||
static constexpr uint32_t MovieData = 4; // UBO: movie playback data
|
||||
static constexpr uint32_t GenericData = 0; // dynamic UBO: generic shader data
|
||||
static constexpr uint32_t Matrices = 1; // dynamic UBO: transform matrices
|
||||
static constexpr uint32_t NanoVGData = 2; // UBO: NanoVG data
|
||||
static constexpr uint32_t DecalInfo = 3; // UBO: per-decal info
|
||||
static constexpr uint32_t MovieData = 4; // UBO: movie playback data
|
||||
}
|
||||
|
||||
// ========== Set layout templates ==========
|
||||
//
|
||||
// These describe the fixed set layouts. Everything downstream that needs to know
|
||||
// which bindings are dynamic, how many there are, or which dynamic-offset slot a
|
||||
// given binding occupies is derived from them below rather than restated, so the
|
||||
// declarations here are the only place that ordering exists.
|
||||
|
||||
inline constexpr DescriptorBindingTemplate MaterialSetBindings[] = {
|
||||
{MaterialBinding::ModelData,
|
||||
vk::DescriptorType::eUniformBufferDynamic,
|
||||
1,
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{MaterialBinding::TextureArray,
|
||||
vk::DescriptorType::eCombinedImageSampler,
|
||||
16,
|
||||
vk::ShaderStageFlagBits::eFragment,
|
||||
vk::ImageViewType::e2DArray},
|
||||
{MaterialBinding::DecalGlobals,
|
||||
vk::DescriptorType::eUniformBuffer,
|
||||
1,
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{MaterialBinding::TransformSSBO, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eVertex},
|
||||
{MaterialBinding::DepthMap, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{MaterialBinding::SceneColor, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{MaterialBinding::DistortionMap, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{MaterialBinding::ShadowMapData, vk::DescriptorType::eUniformBufferDynamic, 1, vk::ShaderStageFlagBits::eVertex},
|
||||
};
|
||||
|
||||
inline constexpr DescriptorBindingTemplate PerDrawSetBindings[] = {
|
||||
{PerDrawBinding::GenericData,
|
||||
vk::DescriptorType::eUniformBufferDynamic,
|
||||
1,
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{PerDrawBinding::Matrices,
|
||||
vk::DescriptorType::eUniformBufferDynamic,
|
||||
1,
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{PerDrawBinding::NanoVGData,
|
||||
vk::DescriptorType::eUniformBuffer,
|
||||
1,
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{PerDrawBinding::DecalInfo,
|
||||
vk::DescriptorType::eUniformBuffer,
|
||||
1,
|
||||
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{PerDrawBinding::MovieData, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
};
|
||||
|
||||
// ========== Dynamic-offset layout, derived from the templates above ==========
|
||||
//
|
||||
// vkCmdBindDescriptorSets consumes a set's dynamic offsets in ascending binding
|
||||
// order, one entry per eUniformBufferDynamic descriptor. Both the count and the
|
||||
// slot each binding occupies are computed from the set templates so a call site
|
||||
// can never disagree with the layout it is binding against — reordering a template
|
||||
// moves the derived slots with it instead of silently feeding a shader the wrong
|
||||
// uniforms.
|
||||
|
||||
template <size_t N>
|
||||
constexpr uint32_t dynamic_offset_count(const DescriptorBindingTemplate (&bindings)[N])
|
||||
{
|
||||
uint32_t count = 0;
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
if (bindings[i].type == vk::DescriptorType::eUniformBufferDynamic) {
|
||||
count += bindings[i].count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Returned by dynamic_offset_slot() when the binding is not a dynamic descriptor of the set.
|
||||
constexpr uint32_t DYNAMIC_SLOT_INVALID = ~0u;
|
||||
|
||||
/**
|
||||
* @brief Position of a dynamic binding within its set's dynamic-offset array
|
||||
*
|
||||
* If @c binding is not a dynamic binding of the given set, @c DYNAMIC_SLOT_INVALID is returned.
|
||||
* Every use below pairs its constant with a static_assert against that sentinel, so a mistyped or
|
||||
* non-dynamic binding constant is a compile error rather than a silent 0 — these constants index
|
||||
* fixed-size dynamic-offset arrays at the bind call sites, so a leaked sentinel would be an
|
||||
* out-of-bounds write.
|
||||
*
|
||||
* Do not "clean this up" back into a `throw` on the not-found path: MSVC rejects a constexpr
|
||||
* function whose flow analysis can reach a throw (C3615), even when no evaluation takes that path.
|
||||
*/
|
||||
template <size_t N>
|
||||
constexpr uint32_t dynamic_offset_slot(const DescriptorBindingTemplate (&bindings)[N], uint32_t binding)
|
||||
{
|
||||
uint32_t slot = 0;
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
if (bindings[i].type != vk::DescriptorType::eUniformBufferDynamic) {
|
||||
continue;
|
||||
}
|
||||
if (bindings[i].binding == binding) {
|
||||
return slot;
|
||||
}
|
||||
slot += bindings[i].count;
|
||||
}
|
||||
return DYNAMIC_SLOT_INVALID;
|
||||
}
|
||||
|
||||
// The Global set declares no dynamic bindings; it contributes nothing to a bind call.
|
||||
constexpr uint32_t GLOBAL_DYNAMIC_OFFSET_COUNT = 0;
|
||||
constexpr uint32_t MATERIAL_DYNAMIC_OFFSET_COUNT = dynamic_offset_count(MaterialSetBindings);
|
||||
constexpr uint32_t PERDRAW_DYNAMIC_OFFSET_COUNT = dynamic_offset_count(PerDrawSetBindings);
|
||||
|
||||
namespace MaterialDynamicSlot {
|
||||
constexpr uint32_t ModelData = dynamic_offset_slot(MaterialSetBindings, MaterialBinding::ModelData);
|
||||
constexpr uint32_t ShadowMapData = dynamic_offset_slot(MaterialSetBindings, MaterialBinding::ShadowMapData);
|
||||
static_assert(ModelData != DYNAMIC_SLOT_INVALID,
|
||||
"MaterialBinding::ModelData is not a dynamic descriptor of the Material set.");
|
||||
static_assert(ShadowMapData != DYNAMIC_SLOT_INVALID,
|
||||
"MaterialBinding::ShadowMapData is not a dynamic descriptor of the Material set.");
|
||||
}
|
||||
|
||||
namespace PerDrawDynamicSlot {
|
||||
constexpr uint32_t GenericData = dynamic_offset_slot(PerDrawSetBindings, PerDrawBinding::GenericData);
|
||||
constexpr uint32_t Matrices = dynamic_offset_slot(PerDrawSetBindings, PerDrawBinding::Matrices);
|
||||
static_assert(GenericData != DYNAMIC_SLOT_INVALID,
|
||||
"PerDrawBinding::GenericData is not a dynamic descriptor of the PerDraw set.");
|
||||
static_assert(Matrices != DYNAMIC_SLOT_INVALID,
|
||||
"PerDrawBinding::Matrices is not a dynamic descriptor of the PerDraw set.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stack-allocated batch writer for descriptor set updates.
|
||||
*
|
||||
* Usage: reset() + writeSet() (pre-fills all bindings with fallbacks)
|
||||
* + setBuffer/setImage overrides for real data + flush().
|
||||
*
|
||||
* Bindings declared as eUniformBufferDynamic get their offset split out of the
|
||||
* descriptor and into the dynamic-offset array (see dynamicOffsets()): setBuffer
|
||||
* writes {buffer, 0, range} into the descriptor and stashes the caller's offset.
|
||||
* That is what lets a set whose only per-draw change is a UBO offset be written
|
||||
* once and rebound many times. Callers must hand dynamicOffsets()/
|
||||
* VulkanDescriptorManager::getDynamicOffsetCount() for a set to
|
||||
* vkCmdBindDescriptorSets when binding it.
|
||||
*/
|
||||
class DescriptorWriter {
|
||||
public:
|
||||
// Fixed capacities for the stack-allocated staging arrays below — NOT Vulkan
|
||||
// hardware limits. They bound a single vkUpdateDescriptorSets batch (one
|
||||
// writeSet), sized to comfortably cover the largest descriptor set layout the
|
||||
// engine builds. Because the backing arrays live on the stack (see m_writes
|
||||
// etc.), the numbers carry no runtime cost beyond that fixed footprint; the
|
||||
// only failure mode is under-sizing, which is Assert-guarded at fill time in
|
||||
// DescriptorWriter::writeSet (e.g. `Assert(m_writeCount < MAX_WRITES)`) so an
|
||||
// oversized layout trips loudly in debug rather than silently overflowing.
|
||||
// Raise these if a future set layout legitimately needs more bindings/infos.
|
||||
static constexpr uint32_t MAX_WRITES = 32; // distinct bindings written per batch
|
||||
static constexpr uint32_t MAX_BUFFER_INFOS = 20; // buffer descriptors staged per batch
|
||||
static constexpr uint32_t MAX_IMAGE_INFOS = 24; // image/sampler descriptors staged per batch
|
||||
static constexpr uint32_t MAX_ACCEL_STRUCT_INFOS = 2; // TLAS descriptors staged per batch (RT)
|
||||
static constexpr uint32_t MAX_BINDINGS_PER_SET = 16; // highest binding number addressable in a set
|
||||
// Most dynamic (eUniformBufferDynamic) bindings any one set layout declares. Derived from
|
||||
// the templates rather than counted by hand, so adding a dynamic binding resizes the
|
||||
// storage that holds its offset automatically.
|
||||
static constexpr uint32_t MAX_DYNAMIC_OFFSETS_PER_SET =
|
||||
MATERIAL_DYNAMIC_OFFSET_COUNT > PERDRAW_DYNAMIC_OFFSET_COUNT ? MATERIAL_DYNAMIC_OFFSET_COUNT
|
||||
: PERDRAW_DYNAMIC_OFFSET_COUNT;
|
||||
|
||||
void reset(vk::Device device, const DescriptorFallbacks& fallbacks) {
|
||||
m_device = device;
|
||||
m_fallbacks = &fallbacks;
|
||||
m_writeCount = 0;
|
||||
m_bufferInfoCount = 0;
|
||||
m_imageInfoCount = 0;
|
||||
m_accelStructInfoCount = 0;
|
||||
m_dynOffsets = {};
|
||||
}
|
||||
|
||||
void writeSet(DescriptorSetIndex setIndex, vk::DescriptorSet set);
|
||||
|
||||
void setBuffer(uint32_t binding, const vk::DescriptorBufferInfo& info);
|
||||
void setImage(uint32_t binding, const vk::DescriptorImageInfo& info);
|
||||
void setImageArray(uint32_t binding, ArrayView<vk::DescriptorImageInfo> infos);
|
||||
|
||||
/**
|
||||
* @brief Dynamic offsets accumulated for a single set, ordered by binding number.
|
||||
*
|
||||
* Only meaningful for a set this writer actually wrote; a set reused from a
|
||||
* memoization cache was not written here, so its caller owns the offsets.
|
||||
* Always sized to the set layout's dynamic descriptor count (offsets for
|
||||
* bindings left at their fallback stay 0). Points at stable per-set storage —
|
||||
* valid until the next writeSet() of that set.
|
||||
*/
|
||||
const uint32_t* dynamicOffsets(DescriptorSetIndex setIndex) const
|
||||
{
|
||||
return m_dynOffsets[static_cast<size_t>(setIndex)].data();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Binds a contiguous run of sets in one vkCmdBindDescriptorSets call
|
||||
*
|
||||
* Concatenates the run's dynamic offsets in set order, which is the layout
|
||||
* vkCmdBindDescriptorSets expects: each set contributes exactly its layout's
|
||||
* dynamic descriptor count (a set declaring none contributes nothing, rather
|
||||
* than padding). @c sets must list the sets for @c firstSet onwards, in order.
|
||||
*
|
||||
* Binding here rather than handing the offsets back to the caller keeps the
|
||||
* concatenation buffer's lifetime contained: it is writer-owned scratch, reused
|
||||
* by the next call, and never outlives the command it was built for.
|
||||
*/
|
||||
void bindSets(vk::CommandBuffer cmd,
|
||||
vk::PipelineLayout layout,
|
||||
DescriptorSetIndex firstSet,
|
||||
ArrayView<vk::DescriptorSet> sets);
|
||||
|
||||
void flush(); // defined in VulkanDescriptorManager.cpp (reports write stats to the manager)
|
||||
|
||||
private:
|
||||
// Per-binding lookup for the current writeSet, indexed by binding number.
|
||||
// Populated by writeSet, used by setBuffer/setImage/setImageArray for O(1) access.
|
||||
struct BindingSlot {
|
||||
vk::DescriptorBufferInfo* bufferInfo = nullptr; // non-null for buffer bindings
|
||||
vk::DescriptorImageInfo* imageInfo = nullptr; // non-null for image bindings
|
||||
uint32_t count = 0; // descriptor count (1 or 16 for arrays)
|
||||
vk::ImageViewType viewType = vk::ImageViewType::e2D; // for fallback lookup
|
||||
int dynIndex = -1; // slot in m_dynOffsets, or -1 if not dynamic
|
||||
};
|
||||
|
||||
vk::Device m_device;
|
||||
const DescriptorFallbacks* m_fallbacks = nullptr;
|
||||
|
||||
std::array<vk::WriteDescriptorSet, MAX_WRITES> m_writes;
|
||||
std::array<vk::DescriptorBufferInfo, MAX_BUFFER_INFOS> m_bufferInfos;
|
||||
std::array<vk::DescriptorImageInfo, MAX_IMAGE_INFOS> m_imageInfos;
|
||||
std::array<vk::AccelerationStructureKHR, MAX_ACCEL_STRUCT_INFOS> m_accelStructInfos;
|
||||
std::array<vk::WriteDescriptorSetAccelerationStructureKHR, MAX_ACCEL_STRUCT_INFOS> m_asWriteInfos;
|
||||
std::array<BindingSlot, MAX_BINDINGS_PER_SET> m_bindingSlots;
|
||||
std::array<std::array<uint32_t, MAX_DYNAMIC_OFFSETS_PER_SET>, static_cast<size_t>(DescriptorSetIndex::Count)>
|
||||
m_dynOffsets{};
|
||||
// Scratch used by bindSets() to concatenate a run of sets' offsets.
|
||||
std::array<uint32_t, static_cast<size_t>(DescriptorSetIndex::Count) * MAX_DYNAMIC_OFFSETS_PER_SET>
|
||||
m_dynOffsetScratch{};
|
||||
DescriptorSetIndex m_currentSet = DescriptorSetIndex::Global;
|
||||
uint32_t m_writeCount = 0;
|
||||
uint32_t m_bufferInfoCount = 0;
|
||||
uint32_t m_imageInfoCount = 0;
|
||||
uint32_t m_accelStructInfoCount = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Manages Vulkan descriptor sets, pools, and layouts
|
||||
@@ -195,8 +375,19 @@ public:
|
||||
// larger chunks waste memory, smaller chunks allocate pools more often.
|
||||
// MAX_SETS_PER_POOL supports ~330 draw calls (3 sets each) per chunk; the per-
|
||||
// type multipliers cover the worst-case bindings a single set can request.
|
||||
// One chunk now covers a dense scene outright: with the dynamic-UBO descriptor
|
||||
// memoization in applyMaterial/renderShadowDraw, a big asteroid field at ~1350
|
||||
// model draws settles at ~250 sets/frame, so growth is back to being the rare
|
||||
// case it was meant to be. (The same scene needed ~2500 sets/frame before that.)
|
||||
// Deliberately left at the original size rather than raised: the per-frame
|
||||
// growth this used to log was a symptom of the churn, not of undersizing, and
|
||||
// FSO targets low-end GPUs where a larger pool is real wasted memory.
|
||||
static constexpr uint32_t MAX_SETS_PER_POOL = 1024;
|
||||
static constexpr uint32_t MAX_UNIFORM_BUFFERS_PER_POOL = MAX_SETS_PER_POOL * 9; // up to 9 UBOs per draw
|
||||
// Worst case per set is 3 static UBOs (Global, PerDraw) and 2 dynamic (Material:
|
||||
// ModelData + ShadowMapData; PerDraw: GenericData + Matrices). Total UBO
|
||||
// capacity per chunk is slightly below what this was before the dynamic split.
|
||||
static constexpr uint32_t MAX_UNIFORM_BUFFERS_PER_POOL = MAX_SETS_PER_POOL * 5; // static UBOs
|
||||
static constexpr uint32_t MAX_DYNAMIC_UNIFORM_BUFFERS_PER_POOL = MAX_SETS_PER_POOL * 2; // dynamic UBOs
|
||||
static constexpr uint32_t MAX_SAMPLERS_PER_POOL = MAX_SETS_PER_POOL * 16; // up to 16 samplers per material set
|
||||
|
||||
VulkanDescriptorManager() = default;
|
||||
@@ -254,6 +445,15 @@ public:
|
||||
*/
|
||||
static const DescriptorSetTemplate& getSetTemplate(DescriptorSetIndex setIndex);
|
||||
|
||||
/**
|
||||
* @brief Number of dynamic (eUniformBufferDynamic) descriptors in a set layout
|
||||
*
|
||||
* vkCmdBindDescriptorSets requires exactly this many entries in pDynamicOffsets,
|
||||
* so every bind site must agree with the layout. VulkanStateTracker asserts on
|
||||
* it; raw cmd.bindDescriptorSets() callers should pass the matching count.
|
||||
*/
|
||||
static uint32_t getDynamicOffsetCount(DescriptorSetIndex setIndex);
|
||||
|
||||
/**
|
||||
* @brief Get descriptor set layout for a given set index
|
||||
*/
|
||||
|
||||
@@ -686,8 +686,12 @@ void VulkanDrawManager::renderModel(model_material* material_info, indexed_verte
|
||||
stateTracker->getCommandBuffer().drawIndexed(dp.indexCount, 1, dp.firstIndex, dp.baseVertex, 0);
|
||||
}
|
||||
|
||||
void VulkanDrawManager::renderShadowDraw(gr_buffer_handle ubo_handle, size_t ubo_offset, size_t ubo_size,
|
||||
vertex_buffer* buffer, indexed_vertex_source* vert_src, size_t texi) const
|
||||
void VulkanDrawManager::renderShadowDraw(gr_buffer_handle ubo_handle,
|
||||
size_t ubo_offset,
|
||||
size_t ubo_size,
|
||||
vertex_buffer* buffer,
|
||||
indexed_vertex_source* vert_src,
|
||||
size_t texi)
|
||||
{
|
||||
if (!buffer || !vert_src) {
|
||||
return;
|
||||
@@ -748,57 +752,96 @@ void VulkanDrawManager::renderShadowDraw(gr_buffer_handle ubo_handle, size_t ubo
|
||||
}
|
||||
|
||||
stateTracker->bindPipeline(pipeline, pipelineManager->getPipelineLayout());
|
||||
stateTracker->setDepthBias(-1024.0f, 0.0f);
|
||||
stateTracker->setDepthBias(1024.0f, 1.0f); // GL shadow pass: gr_zbias(-1024) = polygon offset factor 1, units 1024
|
||||
|
||||
// Bind descriptor sets: shadowMapData (per-draw) and shadowCascadeParams
|
||||
// (bound separately, per-frame, via shadow_cascade_params_bind) both live in
|
||||
// the fixed 3-tier layout alongside the batched-submodel transform buffer;
|
||||
// no material textures are needed for depth-only rendering.
|
||||
//
|
||||
// All three sets are memoized across the shadow pass (see m_cachedShadowSets).
|
||||
// ShadowMapData is a dynamic binding, so the only thing that changes from one
|
||||
// shadow draw to the next -- its UBO offset -- rides in the dynamic-offset array
|
||||
// and leaves the set contents untouched. Without that this path allocated and
|
||||
// fully rewrote three descriptor sets per shadow draw, times the cascade count.
|
||||
{
|
||||
DescriptorWriter writer;
|
||||
writer.reset(descManager->getDevice(), descManager->getFallbacks());
|
||||
|
||||
vk::DescriptorSet globalSet = descManager->allocateFrameSet(DescriptorSetIndex::Global);
|
||||
Assert(globalSet);
|
||||
writer.writeSet(globalSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::Global));
|
||||
ShadowSetInputs shadowInputs;
|
||||
{
|
||||
const auto& pending = getPendingUniformBinding(static_cast<size_t>(uniform_block_type::ShadowCascadeParams));
|
||||
if (pending.valid) {
|
||||
vk::Buffer buf = bufferManager->getVkBuffer(pending.bufferHandle);
|
||||
const auto& cascade =
|
||||
getPendingUniformBinding(static_cast<size_t>(uniform_block_type::ShadowCascadeParams));
|
||||
shadowInputs.cascadeHandle = cascade.bufferHandle.value();
|
||||
shadowInputs.cascadeOffset = cascade.offset;
|
||||
shadowInputs.cascadeSize = cascade.size;
|
||||
shadowInputs.cascadeValid = cascade.valid;
|
||||
shadowInputs.shadowDataHandle = ubo_handle.value();
|
||||
shadowInputs.shadowDataSize = ubo_size; // offset is dynamic, so not part of the key
|
||||
auto& tf = g_transformBuffers[descManager->getCurrentFrame()];
|
||||
if (tf.buffer && tf.lastUploadSize > 0) {
|
||||
shadowInputs.transformBuffer = tf.buffer;
|
||||
shadowInputs.transformOffset = tf.lastUploadOffset;
|
||||
shadowInputs.transformSize = tf.lastUploadSize;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_cachedShadowValid || shadowInputs != m_cachedShadowInputs) {
|
||||
DescriptorWriter writer;
|
||||
writer.reset(descManager->getDevice(), descManager->getFallbacks());
|
||||
|
||||
m_cachedShadowGlobalSet = descManager->allocateFrameSet(DescriptorSetIndex::Global);
|
||||
Assert(m_cachedShadowGlobalSet);
|
||||
writer.writeSet(DescriptorSetIndex::Global, m_cachedShadowGlobalSet);
|
||||
if (shadowInputs.cascadeValid) {
|
||||
vk::Buffer buf = bufferManager->getVkBuffer(gr_buffer_handle(shadowInputs.cascadeHandle));
|
||||
if (buf) {
|
||||
writer.setBuffer(GlobalBinding::ShadowCascadeParams, {buf, pending.offset, pending.size});
|
||||
writer.setBuffer(GlobalBinding::ShadowCascadeParams,
|
||||
{buf, shadowInputs.cascadeOffset, shadowInputs.cascadeSize});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vk::DescriptorSet materialSet = descManager->allocateFrameSet(DescriptorSetIndex::Material);
|
||||
Assert(materialSet);
|
||||
writer.writeSet(materialSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::Material));
|
||||
{
|
||||
vk::Buffer buf = bufferManager->getVkBuffer(ubo_handle);
|
||||
if (buf) {
|
||||
writer.setBuffer(MaterialBinding::ShadowMapData,
|
||||
{buf, static_cast<vk::DeviceSize>(ubo_offset), static_cast<vk::DeviceSize>(ubo_size)});
|
||||
m_cachedShadowMaterialSet = descManager->allocateFrameSet(DescriptorSetIndex::Material);
|
||||
Assert(m_cachedShadowMaterialSet);
|
||||
writer.writeSet(DescriptorSetIndex::Material, m_cachedShadowMaterialSet);
|
||||
{
|
||||
vk::Buffer buf = bufferManager->getVkBuffer(ubo_handle);
|
||||
if (buf) {
|
||||
// Offset 0: the real offset is supplied per draw as a dynamic offset.
|
||||
writer.setBuffer(MaterialBinding::ShadowMapData, {buf, 0, static_cast<vk::DeviceSize>(ubo_size)});
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
uint32_t tfIdx = descManager->getCurrentFrame();
|
||||
auto& tf = g_transformBuffers[tfIdx];
|
||||
if (tf.buffer && tf.lastUploadSize > 0) {
|
||||
writer.setBuffer(MaterialBinding::TransformSSBO, {tf.buffer,
|
||||
static_cast<vk::DeviceSize>(tf.lastUploadOffset),
|
||||
static_cast<vk::DeviceSize>(tf.lastUploadSize)});
|
||||
if (shadowInputs.transformBuffer) {
|
||||
writer.setBuffer(MaterialBinding::TransformSSBO,
|
||||
{shadowInputs.transformBuffer,
|
||||
static_cast<vk::DeviceSize>(shadowInputs.transformOffset),
|
||||
static_cast<vk::DeviceSize>(shadowInputs.transformSize)});
|
||||
}
|
||||
|
||||
m_cachedShadowPerDrawSet = descManager->allocateFrameSet(DescriptorSetIndex::PerDraw);
|
||||
Assert(m_cachedShadowPerDrawSet);
|
||||
writer.writeSet(DescriptorSetIndex::PerDraw, m_cachedShadowPerDrawSet);
|
||||
writer.flush();
|
||||
|
||||
m_cachedShadowInputs = shadowInputs;
|
||||
m_cachedShadowValid = true;
|
||||
}
|
||||
|
||||
vk::DescriptorSet perDrawSet = descManager->allocateFrameSet(DescriptorSetIndex::PerDraw);
|
||||
Assert(perDrawSet);
|
||||
writer.writeSet(perDrawSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::PerDraw));
|
||||
writer.flush();
|
||||
// applyMaterial's caches deliberately are NOT invalidated here. They record
|
||||
// what a descriptor set *contains*, and the shadow pass allocates its own
|
||||
// sets rather than overwriting theirs. Which set is currently bound is
|
||||
// tracked separately by VulkanStateTracker, and this path binds through it,
|
||||
// so the next applyMaterial cache hit still rebinds its set correctly. (That
|
||||
// is what separates this from the ImGui case invalidateDrawStateCaches()
|
||||
// exists for: ImGui binds on the command buffer behind the tracker's back.)
|
||||
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::Global, globalSet);
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::Material, materialSet);
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::PerDraw, perDrawSet);
|
||||
// ShadowMapData is the one thing that moves per shadow draw; every other dynamic binding
|
||||
// on this path holds the fallback buffer and so must stay at offset 0.
|
||||
uint32_t materialDynOffsets[MATERIAL_DYNAMIC_OFFSET_COUNT] = {};
|
||||
materialDynOffsets[MaterialDynamicSlot::ShadowMapData] = static_cast<uint32_t>(ubo_offset);
|
||||
|
||||
static constexpr uint32_t perDrawDynOffsets[PERDRAW_DYNAMIC_OFFSET_COUNT] = {};
|
||||
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::Global, m_cachedShadowGlobalSet, nullptr);
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::Material, m_cachedShadowMaterialSet, materialDynOffsets);
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::PerDraw, m_cachedShadowPerDrawSet, perDrawDynOffsets);
|
||||
}
|
||||
|
||||
vk::Buffer vbuffer = bufferManager->getVkBuffer(vert_src->Vbuffer_handle);
|
||||
@@ -960,6 +1003,10 @@ void VulkanDrawManager::resetFrameStats()
|
||||
m_cachedMaterialValid = false;
|
||||
m_cachedPerDrawSet = nullptr;
|
||||
m_cachedPerDrawValid = false;
|
||||
m_cachedShadowGlobalSet = nullptr;
|
||||
m_cachedShadowMaterialSet = nullptr;
|
||||
m_cachedShadowPerDrawSet = nullptr;
|
||||
m_cachedShadowValid = false;
|
||||
}
|
||||
|
||||
void VulkanDrawManager::printFrameStats()
|
||||
@@ -1283,6 +1330,7 @@ bool VulkanDrawManager::applyMaterial(material* mat, primitive_type prim_type, v
|
||||
|
||||
// Like opengl_tnl_set_material(), every material draw sets the texture addressing (e.g. MR_FORCE_CLAMP skyboxes)
|
||||
setTextureAddressing(mat->get_texture_addressing());
|
||||
vulkan_zbias(mat->get_depth_bias());
|
||||
|
||||
// Build pipeline configuration from material
|
||||
PipelineConfig config = buildPipelineConfig(mat, prim_type);
|
||||
@@ -1348,6 +1396,28 @@ bool VulkanDrawManager::applyMaterial(material* mat, primitive_type prim_type, v
|
||||
}
|
||||
};
|
||||
|
||||
// Dynamic offsets for the Material/PerDraw sets, ordered by binding number.
|
||||
// Computed here rather than read back from the writer because the whole point
|
||||
// of the dynamic bindings is that these change on draws where the set itself
|
||||
// is reused from cache and never written.
|
||||
const auto dynOffsetOf = [&](uniform_block_type blockType) -> uint32_t {
|
||||
const auto& pending = m_pendingUniformBindings[static_cast<size_t>(blockType)];
|
||||
if (!pending.valid || !bufferManager->getVkBuffer(pending.bufferHandle)) {
|
||||
return 0; // falls back to the dummy buffer, which is bound at offset 0
|
||||
}
|
||||
return static_cast<uint32_t>(pending.offset);
|
||||
};
|
||||
// Zero-initialized, then filled by name: MaterialDynamicSlot::ShadowMapData is left at 0
|
||||
// because ShadowMapData is never bound on this path, so its descriptor still holds the
|
||||
// fallback buffer -- bound as {buffer, 0, FALLBACK_UNIFORM_BUFFER_SIZE}, i.e. spanning the
|
||||
// whole buffer, so any non-zero dynamic offset would push offset+range past the end of it.
|
||||
uint32_t materialDynOffsets[MATERIAL_DYNAMIC_OFFSET_COUNT] = {};
|
||||
materialDynOffsets[MaterialDynamicSlot::ModelData] = dynOffsetOf(uniform_block_type::ModelData);
|
||||
|
||||
uint32_t perDrawDynOffsets[PERDRAW_DYNAMIC_OFFSET_COUNT] = {};
|
||||
perDrawDynOffsets[PerDrawDynamicSlot::GenericData] = dynOffsetOf(uniform_block_type::GenericData);
|
||||
perDrawDynOffsets[PerDrawDynamicSlot::Matrices] = dynOffsetOf(uniform_block_type::Matrices);
|
||||
|
||||
// Set 0: Global (memoized per frame — see m_cachedGlobalSet). Rebuilt only
|
||||
// when a Global input changed: a pending Global UBO (m_globalSetDirty set in
|
||||
// setPendingUniformBinding), the shadow TLAS (invalidateGlobalSet from
|
||||
@@ -1359,7 +1429,7 @@ bool VulkanDrawManager::applyMaterial(material* mat, primitive_type prim_type, v
|
||||
if (m_globalSetDirty || !m_cachedGlobalSet || shadowReady != m_cachedGlobalHadShadow) {
|
||||
m_cachedGlobalSet = descManager->allocateFrameSet(DescriptorSetIndex::Global);
|
||||
Assert(m_cachedGlobalSet);
|
||||
writer.writeSet(m_cachedGlobalSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::Global));
|
||||
writer.writeSet(DescriptorSetIndex::Global, m_cachedGlobalSet);
|
||||
bindPendingUBOs(DescriptorSetIndex::Global);
|
||||
if (shadowReady) {
|
||||
writer.setImage(GlobalBinding::ShadowMap, pp->getShadowTextureInfo());
|
||||
@@ -1396,9 +1466,14 @@ bool VulkanDrawManager::applyMaterial(material* mat, primitive_type prim_type, v
|
||||
matInputs.sceneColorInfo = m_sceneColorInfo;
|
||||
matInputs.distMapInfo = m_distMapInfo;
|
||||
{
|
||||
// ModelData's offset is deliberately NOT part of the key: it is a dynamic
|
||||
// binding, so an offset-only change is carried by materialDynOffsets and
|
||||
// leaves the set's contents identical. Buffer and size still matter --
|
||||
// both live in the descriptor.
|
||||
const auto& md = m_pendingUniformBindings[static_cast<size_t>(uniform_block_type::ModelData)];
|
||||
const auto& dg = m_pendingUniformBindings[static_cast<size_t>(uniform_block_type::DecalGlobals)];
|
||||
matInputs.uboHandle[0] = md.bufferHandle.value(); matInputs.uboOffset[0] = md.offset;
|
||||
matInputs.uboHandle[0] = md.bufferHandle.value();
|
||||
matInputs.uboOffset[0] = 0;
|
||||
matInputs.uboSize[0] = md.size; matInputs.uboValid[0] = md.valid;
|
||||
matInputs.uboHandle[1] = dg.bufferHandle.value(); matInputs.uboOffset[1] = dg.offset;
|
||||
matInputs.uboSize[1] = dg.size; matInputs.uboValid[1] = dg.valid;
|
||||
@@ -1412,7 +1487,7 @@ bool VulkanDrawManager::applyMaterial(material* mat, primitive_type prim_type, v
|
||||
} else {
|
||||
materialSet = descManager->allocateFrameSet(DescriptorSetIndex::Material);
|
||||
Assert(materialSet);
|
||||
writer.writeSet(materialSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::Material));
|
||||
writer.writeSet(DescriptorSetIndex::Material, materialSet);
|
||||
bindPendingUBOs(DescriptorSetIndex::Material);
|
||||
if (matInputs.transformBuffer) {
|
||||
writer.setBuffer(MaterialBinding::TransformSSBO,
|
||||
@@ -1432,14 +1507,23 @@ bool VulkanDrawManager::applyMaterial(material* mat, primitive_type prim_type, v
|
||||
// Set 2: PerDraw (previous-set memoized — see m_cachedPerDrawSet)
|
||||
PerDrawSetInputs pdInputs;
|
||||
{
|
||||
static constexpr uniform_block_type pdTypes[NUM_PERDRAW_UBOS] = {
|
||||
uniform_block_type::GenericData, uniform_block_type::Matrices,
|
||||
uniform_block_type::NanoVGData, uniform_block_type::DecalInfo,
|
||||
uniform_block_type::MovieData};
|
||||
struct PerDrawUbo {
|
||||
uniform_block_type blockType;
|
||||
uint32_t binding;
|
||||
};
|
||||
static constexpr PerDrawUbo pdUbos[NUM_PERDRAW_UBOS] = {
|
||||
{uniform_block_type::GenericData, PerDrawBinding::GenericData},
|
||||
{uniform_block_type::Matrices, PerDrawBinding::Matrices},
|
||||
{uniform_block_type::NanoVGData, PerDrawBinding::NanoVGData},
|
||||
{uniform_block_type::DecalInfo, PerDrawBinding::DecalInfo},
|
||||
{uniform_block_type::MovieData, PerDrawBinding::MovieData}};
|
||||
for (int i = 0; i < NUM_PERDRAW_UBOS; ++i) {
|
||||
const auto& p = m_pendingUniformBindings[static_cast<size_t>(pdTypes[i])];
|
||||
const auto& p = m_pendingUniformBindings[static_cast<size_t>(pdUbos[i].blockType)];
|
||||
pdInputs.uboHandle[i] = p.bufferHandle.value();
|
||||
pdInputs.uboOffset[i] = p.offset;
|
||||
// The offset of a dynamic binding rides in perDrawDynOffsets and is not part
|
||||
// of the set's contents.
|
||||
const bool dynamic = dynamic_offset_slot(PerDrawSetBindings, pdUbos[i].binding) != DYNAMIC_SLOT_INVALID;
|
||||
pdInputs.uboOffset[i] = dynamic ? 0 : p.offset;
|
||||
pdInputs.uboSize[i] = p.size;
|
||||
pdInputs.uboValid[i] = p.valid;
|
||||
}
|
||||
@@ -1451,16 +1535,16 @@ bool VulkanDrawManager::applyMaterial(material* mat, primitive_type prim_type, v
|
||||
} else {
|
||||
perDrawSet = descManager->allocateFrameSet(DescriptorSetIndex::PerDraw);
|
||||
Assert(perDrawSet);
|
||||
writer.writeSet(perDrawSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::PerDraw));
|
||||
writer.writeSet(DescriptorSetIndex::PerDraw, perDrawSet);
|
||||
bindPendingUBOs(DescriptorSetIndex::PerDraw);
|
||||
m_cachedPerDrawSet = perDrawSet;
|
||||
m_cachedPerDrawInputs = pdInputs;
|
||||
m_cachedPerDrawValid = true;
|
||||
}
|
||||
writer.flush();
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::Global, globalSet);
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::Material, materialSet);
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::PerDraw, perDrawSet);
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::Global, globalSet, nullptr);
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::Material, materialSet, materialDynOffsets);
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::PerDraw, perDrawSet, perDrawDynOffsets);
|
||||
}
|
||||
|
||||
// Update tracked state for FSO compatibility
|
||||
@@ -1483,9 +1567,6 @@ bool VulkanDrawManager::applyMaterial(material* mat, primitive_type prim_type, v
|
||||
stateTracker->setStencilMode(GR_STENCIL_NONE);
|
||||
}
|
||||
|
||||
// Set depth bias if needed
|
||||
stateTracker->setDepthBias(static_cast<float>(mat->get_depth_bias()), 0.0f);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1543,7 +1624,7 @@ void VulkanDrawManager::draw(primitive_type prim_type, int first_vertex, int ver
|
||||
m_frameStats.totalVertices += vertex_count;
|
||||
|
||||
// Flush any dirty dynamic state (viewport, scissor, depth bias, stencil ref)
|
||||
// before issuing the draw command. applyMaterial sets these AFTER bindPipeline,
|
||||
// before issuing the draw command. applyMaterial sets some of these AFTER bindPipeline,
|
||||
// so they may be dirty even when the pipeline didn't change.
|
||||
stateTracker->applyDynamicState();
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@ class VulkanDrawManager {
|
||||
size_t ubo_size,
|
||||
vertex_buffer* buffer,
|
||||
indexed_vertex_source* vert_src,
|
||||
size_t texi) const;
|
||||
size_t texi);
|
||||
|
||||
/**
|
||||
* @brief Draw a unit sphere with the given material
|
||||
@@ -286,6 +286,26 @@ class VulkanDrawManager {
|
||||
*/
|
||||
void invalidateGlobalSet() { m_globalSetDirty = true; }
|
||||
|
||||
/**
|
||||
* @brief Invalidate every memoized descriptor-set cache (Global + Material + PerDraw)
|
||||
*
|
||||
* These previous-set caches (see the class comments above m_cachedGlobalSet /
|
||||
* m_cachedMaterialSet / m_cachedPerDrawSet) assume nothing rebinds a *different*
|
||||
* descriptor set on the command buffer between applyMaterial() calls behind their
|
||||
* back. Code that renders directly on the command buffer without going through
|
||||
* applyMaterial (currently: ImGui's Vulkan backend, drawing into the same active
|
||||
* composition pass) breaks that assumption -- call this right afterward, alongside
|
||||
* VulkanStateTracker::invalidateExternalBindings(), so the next applyMaterial() rebuilds
|
||||
* and rebinds every set instead of trusting stale cached handles.
|
||||
*/
|
||||
void invalidateDrawStateCaches()
|
||||
{
|
||||
m_globalSetDirty = true;
|
||||
m_cachedMaterialValid = false;
|
||||
m_cachedPerDrawValid = false;
|
||||
m_cachedShadowValid = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get current texture addressing mode
|
||||
*/
|
||||
@@ -449,6 +469,14 @@ class VulkanDrawManager {
|
||||
mutable FrameStats m_frameStats;
|
||||
int m_frameStatsFrameNum = 0;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Read-only access to this frame's diagnostic counters, e.g. for the ImGui profiler
|
||||
* overlay's -gr_debug section (see printFrameStats() for the nprintf equivalent)
|
||||
*/
|
||||
const FrameStats& getFrameStats() const { return m_frameStats; }
|
||||
|
||||
private:
|
||||
// First-N debug-log counter for on-demand texture binds; a member rather
|
||||
// than a function-local static so it resets on renderer restart. mutable because
|
||||
// bindMaterialTextures is const. Gates nprintf spam only.
|
||||
@@ -540,6 +568,41 @@ class VulkanDrawManager {
|
||||
PerDrawSetInputs m_cachedPerDrawInputs;
|
||||
bool m_cachedPerDrawValid = false;
|
||||
|
||||
// ---- Shadow-pass descriptor memoization (renderShadowDraw) ----
|
||||
// The shadow pass doesn't go through applyMaterial, so it gets its own cache.
|
||||
// All three of its sets are constant across the whole pass: the only per-draw
|
||||
// input, the ShadowMapData UBO offset, is a dynamic binding and therefore not
|
||||
// part of the set contents. Reset per frame with the rest (resetFrameStats).
|
||||
struct ShadowSetInputs {
|
||||
int cascadeHandle = -1;
|
||||
vk::DeviceSize cascadeOffset = 0;
|
||||
vk::DeviceSize cascadeSize = 0;
|
||||
bool cascadeValid = false;
|
||||
int shadowDataHandle = -1;
|
||||
size_t shadowDataSize = 0;
|
||||
vk::Buffer transformBuffer = nullptr;
|
||||
size_t transformOffset = 0;
|
||||
size_t transformSize = 0;
|
||||
|
||||
bool operator==(const ShadowSetInputs& o) const
|
||||
{
|
||||
return cascadeHandle == o.cascadeHandle && cascadeOffset == o.cascadeOffset &&
|
||||
cascadeSize == o.cascadeSize && cascadeValid == o.cascadeValid &&
|
||||
shadowDataHandle == o.shadowDataHandle && shadowDataSize == o.shadowDataSize &&
|
||||
transformBuffer == o.transformBuffer && transformOffset == o.transformOffset &&
|
||||
transformSize == o.transformSize;
|
||||
}
|
||||
bool operator!=(const ShadowSetInputs& o) const
|
||||
{
|
||||
return !(*this == o);
|
||||
}
|
||||
};
|
||||
vk::DescriptorSet m_cachedShadowGlobalSet = nullptr;
|
||||
vk::DescriptorSet m_cachedShadowMaterialSet = nullptr;
|
||||
vk::DescriptorSet m_cachedShadowPerDrawSet = nullptr;
|
||||
ShadowSetInputs m_cachedShadowInputs;
|
||||
bool m_cachedShadowValid = false;
|
||||
|
||||
// Texture overrides for material bindings 4-6.
|
||||
vk::DescriptorImageInfo m_depthTextureInfo; // binding 4: depth/position for soft particles
|
||||
vk::DescriptorImageInfo m_sceneColorInfo; // binding 5: scene color for distortion
|
||||
|
||||
@@ -98,8 +98,7 @@ void vulkan_set_default_material_uniforms(material* material_info)
|
||||
(clip_plane.normal.xyz.y * clip_plane.position.xyz.y) +
|
||||
(clip_plane.normal.xyz.z * clip_plane.position.xyz.z));
|
||||
|
||||
// Model matrix (identity for now, material doesn't provide one)
|
||||
vm_matrix4_set_identity(&data->modelMatrix);
|
||||
data->modelMatrix = gr_model_matrix_stack.get_transform();
|
||||
} else {
|
||||
data->clipEnabled = 0;
|
||||
vm_matrix4_set_identity(&data->modelMatrix);
|
||||
@@ -613,6 +612,9 @@ void vulkan_calculate_irrmap()
|
||||
}
|
||||
|
||||
// Get envmap cubemap view
|
||||
if (!texManager->ensureCubemapUploaded(ENVMAP)) {
|
||||
return;
|
||||
}
|
||||
auto* envSlot = bm_get_slot(ENVMAP, true);
|
||||
if (!envSlot || !envSlot->gr_info) {
|
||||
return;
|
||||
@@ -638,9 +640,6 @@ void vulkan_calculate_irrmap()
|
||||
|
||||
vk::CommandBuffer cmd = stateTracker->getCommandBuffer();
|
||||
|
||||
// End the current swap chain render pass
|
||||
cmd.endRenderPass();
|
||||
|
||||
// Create pipeline for irradiance map generation
|
||||
PipelineConfig config;
|
||||
config.shaderType = SDR_TYPE_IRRADIANCE_MAP_GEN;
|
||||
@@ -711,6 +710,9 @@ void vulkan_calculate_irrmap()
|
||||
|
||||
vk::Extent2D irrExtent(irrTs->width, irrTs->height);
|
||||
|
||||
// End the swap chain pass only after the early returns above, which never resume it
|
||||
cmd.endRenderPass();
|
||||
|
||||
for (size_t face = 0; face < irrTs->cubeFaceFramebuffers.size(); face++) {
|
||||
vk::Framebuffer fb = irrTs->cubeFaceFramebuffers[face];
|
||||
if (!fb) {
|
||||
@@ -753,15 +755,16 @@ void vulkan_calculate_irrmap()
|
||||
// Set 0: Global (all fallback)
|
||||
vk::DescriptorSet globalSet = descManager->allocateFrameSet(DescriptorSetIndex::Global);
|
||||
Assert(globalSet);
|
||||
writer.writeSet(globalSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::Global));
|
||||
writer.writeSet(DescriptorSetIndex::Global, globalSet);
|
||||
|
||||
// Set 1: Material (envmap cubemap at element 0 of texture array)
|
||||
vk::DescriptorSet materialSet = descManager->allocateFrameSet(DescriptorSetIndex::Material);
|
||||
Assert(materialSet);
|
||||
writer.writeSet(materialSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::Material));
|
||||
writer.writeSet(DescriptorSetIndex::Material, materialSet);
|
||||
{
|
||||
std::array<vk::DescriptorImageInfo, VulkanDescriptorManager::MAX_TEXTURE_BINDINGS> texImages;
|
||||
texImages.fill(descManager->getFallbacks().texture2D);
|
||||
// irrmap-f.sdr declares this binding as samplerCube, so every element needs a cube view
|
||||
texImages.fill(descManager->getFallbacks().textureCube);
|
||||
texImages[0].imageView = envmapView;
|
||||
writer.setImageArray(MaterialBinding::TextureArray, texImages);
|
||||
}
|
||||
@@ -769,14 +772,14 @@ void vulkan_calculate_irrmap()
|
||||
// Set 2: PerDraw (face UBO at binding 0)
|
||||
vk::DescriptorSet perDrawSet = descManager->allocateFrameSet(DescriptorSetIndex::PerDraw);
|
||||
Assert(perDrawSet);
|
||||
writer.writeSet(perDrawSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::PerDraw));
|
||||
writer.writeSet(DescriptorSetIndex::PerDraw, perDrawSet);
|
||||
writer.setBuffer(PerDrawBinding::GenericData, {faceUBO,
|
||||
static_cast<vk::DeviceSize>(face) * UBO_SLOT_SIZE, UBO_SLOT_SIZE});
|
||||
writer.flush();
|
||||
|
||||
// Bind all descriptor sets
|
||||
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout,
|
||||
0, {globalSet, materialSet, perDrawSet}, {});
|
||||
const vk::DescriptorSet sets[] = {globalSet, materialSet, perDrawSet};
|
||||
writer.bindSets(cmd, pipelineLayout, DescriptorSetIndex::Global, sets);
|
||||
|
||||
// Draw fullscreen triangle
|
||||
cmd.draw(3, 1, 0, 0);
|
||||
|
||||
@@ -140,7 +140,8 @@ VmaMemoryUsage VulkanMemoryManager::toVmaUsage(MemoryUsage usage)
|
||||
}
|
||||
}
|
||||
|
||||
bool VulkanMemoryManager::allocateBufferMemory(vk::Buffer buffer, MemoryUsage usage, VulkanAllocation& allocation)
|
||||
bool VulkanMemoryManager::allocateBufferMemory(vk::Buffer buffer, MemoryUsage usage, VulkanAllocation& allocation,
|
||||
MemoryPurpose purpose)
|
||||
{
|
||||
if (!m_initialized) {
|
||||
nprintf(("vulkan", "VulkanMemoryManager::allocateBufferMemory called before initialization!\n"));
|
||||
@@ -176,14 +177,17 @@ bool VulkanMemoryManager::allocateBufferMemory(vk::Buffer buffer, MemoryUsage us
|
||||
|
||||
allocation.size = allocInfo.size;
|
||||
allocation.mappedPtr = allocInfo.pMappedData;
|
||||
allocation.purpose = purpose;
|
||||
|
||||
++m_allocationCount;
|
||||
m_totalAllocatedBytes += static_cast<size_t>(allocation.size);
|
||||
m_bytesByPurpose[static_cast<size_t>(purpose)] += static_cast<size_t>(allocation.size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanMemoryManager::allocateImageMemory(vk::Image image, MemoryUsage usage, VulkanAllocation& allocation)
|
||||
bool VulkanMemoryManager::allocateImageMemory(vk::Image image, MemoryUsage usage, VulkanAllocation& allocation,
|
||||
MemoryPurpose purpose)
|
||||
{
|
||||
if (!m_initialized) {
|
||||
nprintf(("vulkan", "VulkanMemoryManager::allocateImageMemory called before initialization!\n"));
|
||||
@@ -219,9 +223,11 @@ bool VulkanMemoryManager::allocateImageMemory(vk::Image image, MemoryUsage usage
|
||||
|
||||
allocation.size = allocInfo.size;
|
||||
allocation.mappedPtr = allocInfo.pMappedData;
|
||||
allocation.purpose = purpose;
|
||||
|
||||
++m_allocationCount;
|
||||
m_totalAllocatedBytes += static_cast<size_t>(allocation.size);
|
||||
m_bytesByPurpose[static_cast<size_t>(purpose)] += static_cast<size_t>(allocation.size);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -242,10 +248,12 @@ void VulkanMemoryManager::freeAllocation(VulkanAllocation& allocation)
|
||||
|
||||
--m_allocationCount;
|
||||
m_totalAllocatedBytes -= static_cast<size_t>(allocation.size);
|
||||
m_bytesByPurpose[static_cast<size_t>(allocation.purpose)] -= static_cast<size_t>(allocation.size);
|
||||
|
||||
allocation.vmaAlloc = VK_NULL_HANDLE;
|
||||
allocation.size = 0;
|
||||
allocation.mappedPtr = nullptr;
|
||||
allocation.purpose = MemoryPurpose::Unknown;
|
||||
}
|
||||
|
||||
void* VulkanMemoryManager::mapMemory(VulkanAllocation& allocation)
|
||||
|
||||
@@ -30,6 +30,19 @@ class VulkanRenderer;
|
||||
// above in lockstep, or VMA's internal assert will fire.
|
||||
constexpr uint32_t VulkanApiVersion = VK_API_VERSION_1_2;
|
||||
|
||||
/**
|
||||
* @brief What an allocation is used for, for the profiler overlay's per-purpose GPU memory
|
||||
* breakdown. Unknown covers anything not worth categorizing for that panel (transient staging
|
||||
* buffers, uniform buffers -- the latter are already tracked separately via
|
||||
* UniformBufferManager/gr_debug_stats).
|
||||
*/
|
||||
enum class MemoryPurpose {
|
||||
Unknown,
|
||||
Texture,
|
||||
Geometry,
|
||||
RenderTarget
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Memory allocation info returned when allocating GPU memory.
|
||||
*
|
||||
@@ -40,6 +53,7 @@ struct VulkanAllocation {
|
||||
VmaAllocation vmaAlloc = VK_NULL_HANDLE;
|
||||
vk::DeviceSize size = 0;
|
||||
void* mappedPtr = nullptr; // Non-null if memory is persistently mapped
|
||||
MemoryPurpose purpose = MemoryPurpose::Unknown;
|
||||
|
||||
bool isValid() const { return vmaAlloc != VK_NULL_HANDLE; }
|
||||
};
|
||||
@@ -94,18 +108,24 @@ public:
|
||||
* @param buffer The buffer to allocate memory for
|
||||
* @param usage The intended memory usage pattern
|
||||
* @param[out] allocation Output allocation info
|
||||
* @param purpose What the buffer is used for, for the per-purpose memory breakdown. Defaults
|
||||
* to Unknown for callers that don't care about that breakdown (staging buffers, etc).
|
||||
* @return true on success
|
||||
*/
|
||||
bool allocateBufferMemory(vk::Buffer buffer, MemoryUsage usage, VulkanAllocation& allocation);
|
||||
bool allocateBufferMemory(vk::Buffer buffer, MemoryUsage usage, VulkanAllocation& allocation,
|
||||
MemoryPurpose purpose = MemoryPurpose::Unknown);
|
||||
|
||||
/**
|
||||
* @brief Allocate memory for an image
|
||||
* @param image The image to allocate memory for
|
||||
* @param usage The intended memory usage pattern
|
||||
* @param[out] allocation Output allocation info
|
||||
* @param purpose What the image is used for, for the per-purpose memory breakdown. Defaults
|
||||
* to Unknown for callers that don't care about that breakdown.
|
||||
* @return true on success
|
||||
*/
|
||||
bool allocateImageMemory(vk::Image image, MemoryUsage usage, VulkanAllocation& allocation);
|
||||
bool allocateImageMemory(vk::Image image, MemoryUsage usage, VulkanAllocation& allocation,
|
||||
MemoryPurpose purpose = MemoryPurpose::Unknown);
|
||||
|
||||
/**
|
||||
* @brief Free a previous allocation
|
||||
@@ -148,6 +168,10 @@ public:
|
||||
size_t getAllocationCount() const { return m_allocationCount; }
|
||||
size_t getTotalAllocatedBytes() const { return m_totalAllocatedBytes; }
|
||||
|
||||
size_t getTextureBytes() const { return m_bytesByPurpose[static_cast<size_t>(MemoryPurpose::Texture)]; }
|
||||
size_t getGeometryBytes() const { return m_bytesByPurpose[static_cast<size_t>(MemoryPurpose::Geometry)]; }
|
||||
size_t getRenderTargetBytes() const { return m_bytesByPurpose[static_cast<size_t>(MemoryPurpose::RenderTarget)]; }
|
||||
|
||||
private:
|
||||
static VmaMemoryUsage toVmaUsage(MemoryUsage usage);
|
||||
|
||||
@@ -155,6 +179,7 @@ private:
|
||||
|
||||
size_t m_allocationCount = 0;
|
||||
size_t m_totalAllocatedBytes = 0;
|
||||
size_t m_bytesByPurpose[4] = {}; // indexed by MemoryPurpose
|
||||
|
||||
bool m_initialized = false;
|
||||
};
|
||||
|
||||
@@ -394,9 +394,26 @@ vk::UniquePipeline VulkanPipelineManager::createPipeline(const PipelineConfig& c
|
||||
}
|
||||
|
||||
// Input assembly
|
||||
//
|
||||
// Primitive restart is enabled for every topology that permits it, rather than disabled
|
||||
// outright. Metal always performs primitive restart on indexed draws and cannot turn it off, so
|
||||
// MoltenVK rejects a strip/fan pipeline that asks to disable it -- vkCreateGraphicsPipelines
|
||||
// fails with VK_ERROR_FEATURE_NOT_PRESENT ("Metal does not support disabling primitive
|
||||
// restart"). getPipeline() then returns a null pipeline and the draw is skipped, which on macOS
|
||||
// silently dropped the entire 2D/UI/HUD path (gr_bitmap and friends render quads as
|
||||
// PRIM_TYPE_TRISTRIP; see graphics/render.cpp) while leaving ImGui -- which builds its own
|
||||
// TRIANGLE_LIST pipeline -- as the only thing still on screen.
|
||||
//
|
||||
// Enabling it changes nothing for FSO's geometry: an index buffer is promoted to 32-bit as soon
|
||||
// as a mesh reaches USHRT_MAX vertices (VB_FLAG_LARGE_INDEX, see modelinterp.cpp), so 0xFFFF is
|
||||
// never a real 16-bit index, and 0xFFFFFFFF is unreachable for 32-bit ones. Neither restart
|
||||
// sentinel can collide with live index data.
|
||||
//
|
||||
// List topologies must keep it disabled: the spec only allows primitiveRestartEnable on them
|
||||
// with VK_EXT_primitive_topology_list_restart, which FSO does not request.
|
||||
vk::PipelineInputAssemblyStateCreateInfo inputAssembly;
|
||||
inputAssembly.topology = convertPrimitiveType(config.primitiveType);
|
||||
inputAssembly.primitiveRestartEnable = VK_FALSE;
|
||||
inputAssembly.primitiveRestartEnable = topologySupportsPrimitiveRestart(inputAssembly.topology);
|
||||
|
||||
// Viewport state (dynamic)
|
||||
vk::PipelineViewportStateCreateInfo viewportState;
|
||||
|
||||
@@ -687,7 +687,7 @@ void VulkanPostProcessor::blitToSwapChain(vk::CommandBuffer cmd)
|
||||
// Set 1: Material — source texture at array slot 0
|
||||
vk::DescriptorSet materialSet = descriptorMgr->allocateFrameSet(DescriptorSetIndex::Material);
|
||||
Assert(materialSet);
|
||||
writer.writeSet(materialSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::Material));
|
||||
writer.writeSet(DescriptorSetIndex::Material, materialSet);
|
||||
{
|
||||
std::array<vk::DescriptorImageInfo, VulkanDescriptorManager::MAX_TEXTURE_BINDINGS> texArrayInfos;
|
||||
texArrayInfos.fill(descriptorMgr->getFallbacks().texture2D);
|
||||
@@ -701,7 +701,7 @@ void VulkanPostProcessor::blitToSwapChain(vk::CommandBuffer cmd)
|
||||
// Set 2: PerDraw — tonemapping UBO (from the per-frame scratch ring)
|
||||
vk::DescriptorSet perDrawSet = descriptorMgr->allocateFrameSet(DescriptorSetIndex::PerDraw);
|
||||
Assert(perDrawSet);
|
||||
writer.writeSet(perDrawSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::PerDraw));
|
||||
writer.writeSet(DescriptorSetIndex::PerDraw, perDrawSet);
|
||||
|
||||
// LDR path: passthrough (tonemapping already ran). Fallback path: live
|
||||
// parameters from the engine lighting profile.
|
||||
@@ -732,8 +732,12 @@ void VulkanPostProcessor::blitToSwapChain(vk::CommandBuffer cmd)
|
||||
{m_ctx.scratchRing.buffer(), slotOffset, m_ctx.scratchRing.slotSize()});
|
||||
}
|
||||
writer.flush();
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::Material, materialSet);
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::PerDraw, perDrawSet);
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::Material,
|
||||
materialSet,
|
||||
writer.dynamicOffsets(DescriptorSetIndex::Material));
|
||||
stateTracker->bindDescriptorSet(DescriptorSetIndex::PerDraw,
|
||||
perDrawSet,
|
||||
writer.dynamicOffsets(DescriptorSetIndex::PerDraw));
|
||||
|
||||
// Draw fullscreen triangle (3 vertices from gl_VertexIndex, no vertex buffer)
|
||||
cmd.draw(3, 1, 0, 0);
|
||||
@@ -800,7 +804,7 @@ void VulkanPostProcessor::encodeToSwapChainPass(vk::CommandBuffer cmd, vk::Rende
|
||||
|
||||
vk::DescriptorSet materialSet = descriptorMgr->allocateFrameSet(DescriptorSetIndex::Material);
|
||||
Assert(materialSet);
|
||||
writer.writeSet(materialSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::Material));
|
||||
writer.writeSet(DescriptorSetIndex::Material, materialSet);
|
||||
{
|
||||
std::array<vk::DescriptorImageInfo, VulkanDescriptorManager::MAX_TEXTURE_BINDINGS> texArrayInfos;
|
||||
texArrayInfos.fill(descriptorMgr->getFallbacks().texture2D);
|
||||
@@ -811,7 +815,7 @@ void VulkanPostProcessor::encodeToSwapChainPass(vk::CommandBuffer cmd, vk::Rende
|
||||
|
||||
vk::DescriptorSet perDrawSet = descriptorMgr->allocateFrameSet(DescriptorSetIndex::PerDraw);
|
||||
Assert(perDrawSet);
|
||||
writer.writeSet(perDrawSet, VulkanDescriptorManager::getSetTemplate(DescriptorSetIndex::PerDraw));
|
||||
writer.writeSet(DescriptorSetIndex::PerDraw, perDrawSet);
|
||||
{
|
||||
vk::DeviceSize slotOffset =
|
||||
m_ctx.scratchRing.alloc(descriptorMgr->getCurrentFrame(), uboData, uboSize);
|
||||
@@ -820,9 +824,8 @@ void VulkanPostProcessor::encodeToSwapChainPass(vk::CommandBuffer cmd, vk::Rende
|
||||
}
|
||||
writer.flush();
|
||||
|
||||
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout,
|
||||
static_cast<uint32_t>(DescriptorSetIndex::Material),
|
||||
{materialSet, perDrawSet}, {});
|
||||
const vk::DescriptorSet sets[] = {materialSet, perDrawSet};
|
||||
writer.bindSets(cmd, pipelineLayout, DescriptorSetIndex::Material, sets);
|
||||
|
||||
cmd.draw(3, 1, 0, 0);
|
||||
cmd.endRenderPass();
|
||||
|
||||
@@ -170,7 +170,8 @@ bool VulkanBloom::createTargets()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_ctx->memoryManager->allocateImageMemory(m_tex[i].image, MemoryUsage::GpuOnly, m_tex[i].allocation)) {
|
||||
if (!m_ctx->memoryManager->allocateImageMemory(
|
||||
m_tex[i].image, MemoryUsage::GpuOnly, m_tex[i].allocation, MemoryPurpose::RenderTarget)) {
|
||||
nprintf(("vulkan", "VulkanBloom: Failed to allocate bloom image %zu memory!\n", i));
|
||||
return false;
|
||||
}
|
||||
|
||||
Loaded 100 of 228 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user