mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 22:56:30 -10:00
Compare commits
65
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76286c5332 | ||
|
|
e4546de24e | ||
|
|
336f198594 | ||
|
|
5c7059b451 | ||
|
|
4c0ff8414d | ||
|
|
019c4bacea | ||
|
|
e7d58d85cb | ||
|
|
84fa0a622f | ||
|
|
c5b761ca5d | ||
|
|
9443c4abe0 | ||
|
|
5e745f7535 | ||
|
|
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
|
||||
}
|
||||
|
||||
@@ -883,6 +883,16 @@ const char *bm_get_filename(int handle) {
|
||||
return entry->filename;
|
||||
}
|
||||
|
||||
const char *bm_get_image_filename(int handle) {
|
||||
auto entry = bm_get_entry(handle);
|
||||
|
||||
// an EFF entry stores the file of its individual image separately
|
||||
if (entry->type == BM_TYPE_EFF)
|
||||
return entry->info.ani.eff.filename;
|
||||
|
||||
return entry->filename;
|
||||
}
|
||||
|
||||
void bm_get_filename(int bitmapnum, char *filename) {
|
||||
if (!bm_is_valid(bitmapnum)) {
|
||||
strcpy(filename, "");
|
||||
@@ -1036,6 +1046,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;
|
||||
|
||||
@@ -436,6 +436,13 @@ void bm_get_filename(int bitmapnum, char *filename);
|
||||
*/
|
||||
const char *bm_get_filename(int handle);
|
||||
|
||||
/**
|
||||
* @brief Gets the name of the file that holds the pixels of this bitmap
|
||||
*
|
||||
* This is the same as bm_get_filename(), except for an EFF. For an EFF it is the file of the individual image.
|
||||
*/
|
||||
const char *bm_get_image_filename(int handle);
|
||||
|
||||
/**
|
||||
* @brief Unloads all used bitmaps, should only ever be called by game_shutdown()
|
||||
*
|
||||
@@ -594,6 +601,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
|
||||
@@ -18,13 +26,14 @@ layout(set = 1, binding = 1) uniform sampler2D sTextures[16];
|
||||
#define SpecBuffer sTextures[3]
|
||||
|
||||
layout(set = 0, binding = 2) uniform sampler2DArrayShadow shadow_map;
|
||||
layout(set = 0, binding = 7) uniform sampler2DArray shadow_map_raw;
|
||||
|
||||
#ifdef ENV_MAP
|
||||
layout(set = 0, binding = 3) uniform samplerCube sEnvmap;
|
||||
layout(set = 0, binding = 4) uniform samplerCube sIrrmap;
|
||||
#endif
|
||||
|
||||
#ifdef RT_SHADOWS
|
||||
#if defined(RT_SHADOWS) || defined(RTAO)
|
||||
layout(set = 0, binding = 5) uniform accelerationStructureEXT shadow_tlas;
|
||||
#endif
|
||||
#else
|
||||
@@ -33,6 +42,7 @@ uniform sampler2D NormalBuffer;
|
||||
uniform sampler2D PositionBuffer;
|
||||
uniform sampler2D SpecBuffer;
|
||||
uniform sampler2DArrayShadow shadow_map;
|
||||
uniform sampler2DArray shadow_map_raw;
|
||||
|
||||
#ifdef ENV_MAP
|
||||
uniform samplerCube sEnvmap;
|
||||
@@ -63,6 +73,10 @@ uniform shadowCascadeParams {
|
||||
int cascade_count;
|
||||
float rtShadowBiasMin;
|
||||
float rtShadowBiasMax;
|
||||
int rtShadowSampleCount;
|
||||
int rtaoSampleCount;
|
||||
float rtaoRadius;
|
||||
float rtaoStrength;
|
||||
|
||||
mat4 shadow_mv_matrix;
|
||||
|
||||
@@ -72,6 +86,7 @@ uniform shadowCascadeParams {
|
||||
mat4 shadow_proj_matrix[NUM_SHADOW_CASCADES];
|
||||
vec4 cascade_distances[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
|
||||
vec4 smoothness_factors[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
|
||||
vec4 penumbra_scale[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
|
||||
};
|
||||
|
||||
#ifdef VULKAN
|
||||
@@ -145,7 +160,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 +203,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 +250,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 +272,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;
|
||||
@@ -295,6 +310,18 @@ void main()
|
||||
|
||||
if (lightType == LT_AMBIENT) {
|
||||
float ao = position_buffer.w;
|
||||
#ifdef RTAO
|
||||
{
|
||||
// Raytraced AO multiplies into the same ao term as the baked AO maps,
|
||||
// so the env-map/IBL ambient below darkens consistently for free.
|
||||
// Ray origin in TLAS-relative space, as for the shadow rays below.
|
||||
vec3 worldPos = mat3(inv_view_matrix) * position + shadow_ray_view_origin;
|
||||
vec3 worldNormal = normalize((inv_view_matrix * vec4(normal, 0.0)).xyz);
|
||||
float rtaoBias = computeRtShadowBias(length(position), rtShadowBiasMin, rtShadowBiasMax);
|
||||
ao *= traceAmbientOcclusion(shadow_tlas, worldPos, worldNormal,
|
||||
rtaoRadius, rtaoStrength, rtaoSampleCount, rtaoBias, gl_FragCoord.xy);
|
||||
}
|
||||
#endif
|
||||
fragmentColor.rgb = diffuseLightColor * diffColor * ao;
|
||||
|
||||
#ifdef ENV_MAP
|
||||
@@ -322,7 +349,14 @@ void main()
|
||||
vec3 worldLightDir = normalize((inv_view_matrix * vec4(lightDir, 0.0)).xyz);
|
||||
|
||||
float rtShadowBias = computeRtShadowBias(length(position), rtShadowBiasMin, rtShadowBiasMax);
|
||||
attenuation *= traceShadowRay(shadow_tlas, worldPos, worldNormal, worldLightDir, lightDist, rtShadowBias, uint(shadow_ray_cull_mask));
|
||||
// Penumbra cone: for directional lights sourceRadius IS the tangent of the
|
||||
// sun's angular radius ($SunAngularSize); for local lights the subtended
|
||||
// half-angle follows from the physical source radius and the light distance.
|
||||
float coneTan = (lightType == LT_DIRECTIONAL)
|
||||
? sourceRadius
|
||||
: sourceRadius / max(lightDist, 1e-3);
|
||||
attenuation *= traceShadowRayCone(shadow_tlas, worldPos, worldNormal, worldLightDir, lightDist,
|
||||
rtShadowBias, uint(shadow_ray_cull_mask), coneTan, rtShadowSampleCount, gl_FragCoord.xy);
|
||||
#else
|
||||
vec4 fragShadowPos = shadow_mv_matrix * inv_view_matrix * vec4(position, 1.0);
|
||||
vec4 fragShadowUV[NUM_SHADOW_CASCADES];
|
||||
@@ -330,7 +364,7 @@ void main()
|
||||
fragShadowUV[i] = transformToShadowMap(shadow_proj_matrix[i], i, fragShadowPos);
|
||||
}
|
||||
|
||||
attenuation *= getShadowValue(shadow_map, -position.z, fragShadowUV, cascade_distances, smoothness_factors, cascade_offset, cascade_count);
|
||||
attenuation *= getShadowValue(shadow_map, shadow_map_raw, -position.z, fragShadowUV, cascade_distances, smoothness_factors, penumbra_scale, cascade_offset, cascade_count);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -338,6 +372,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;
|
||||
@@ -8,9 +11,14 @@ void main() {
|
||||
}
|
||||
#else
|
||||
in vec4 vertPosition;
|
||||
in vec4 vertTexCoord;
|
||||
|
||||
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;
|
||||
@@ -20,6 +28,8 @@ layout (std140) uniform genericData {
|
||||
void main() {
|
||||
gl_Position = vertPosition;
|
||||
v_rcpFrame = vec2(1.0/rt_w, 1.0/rt_h);
|
||||
v_pos = vertPosition.xy*0.5 + 0.5;
|
||||
// Use the real texcoord rather than deriving it from vertPosition: the draw call may ask for a
|
||||
// sub-rectangle of the source texture, which the clip-space formula ignored. Matches post-v.sdr.
|
||||
v_pos = vertTexCoord.xy;
|
||||
}
|
||||
#endif
|
||||
@@ -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"
|
||||
@@ -101,6 +110,10 @@ uniform shadowCascadeParams {
|
||||
int cascade_count;
|
||||
float rtShadowBiasMin;
|
||||
float rtShadowBiasMax;
|
||||
int rtShadowSampleCount;
|
||||
int rtaoSampleCount;
|
||||
float rtaoRadius;
|
||||
float rtaoStrength;
|
||||
|
||||
mat4 shadow_mv_matrix;
|
||||
|
||||
@@ -110,8 +123,16 @@ uniform shadowCascadeParams {
|
||||
mat4 shadow_proj_matrix[NUM_SHADOW_CASCADES];
|
||||
vec4 cascade_distances[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
|
||||
vec4 smoothness_factors[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
|
||||
vec4 penumbra_scale[(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]
|
||||
@@ -122,6 +143,7 @@ layout(set = 1, binding = 1) uniform sampler2DArray materialTextures[16];
|
||||
#define sMiscmap materialTextures[6]
|
||||
|
||||
layout(set = 0, binding = 2) uniform sampler2DArrayShadow shadow_map;
|
||||
layout(set = 0, binding = 7) uniform sampler2DArray shadow_map_raw;
|
||||
layout(set = 1, binding = 5) uniform sampler2D sFramebuffer;
|
||||
|
||||
#ifdef MODEL_SDR_FLAG_RT_SHADOWS
|
||||
@@ -139,13 +161,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;
|
||||
@@ -168,6 +189,7 @@ uniform sampler2DArray sMiscmap;
|
||||
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_MISC
|
||||
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_SHADOWS
|
||||
uniform sampler2DArrayShadow shadow_map;
|
||||
uniform sampler2DArray shadow_map_raw;
|
||||
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_SHADOWS
|
||||
|
||||
in VertexOutput {
|
||||
@@ -185,13 +207,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)
|
||||
@@ -255,7 +274,11 @@ vec3 CalculateLighting(vec3 normal, vec3 diffuseMaterial, vec3 specularMaterial,
|
||||
if (rtShadowsActive && lights[i].light_type == LT_DIRECTIONAL && shadowedDirectionalCount < MAX_RT_SHADOW_LIGHTS) {
|
||||
vec3 worldSunDir = normalize((invView * vec4(lights[i].position.xyz, 0.0)).xyz);
|
||||
float rtShadowBias = computeRtShadowBias(length(vertIn.position.xyz), rtShadowBiasMin, rtShadowBiasMax);
|
||||
shadow = traceShadowRay(shadow_tlas, worldPos, worldNormal, worldSunDir, RT_SHADOW_MAX_DISTANCE, rtShadowBias, uint(shadow_ray_cull_mask));
|
||||
// For directional lights ml_sourceRadius is the tangent of the sun's
|
||||
// angular radius ($SunAngularSize) -- 0 keeps hard shadows.
|
||||
shadow = traceShadowRayCone(shadow_tlas, worldPos, worldNormal, worldSunDir, RT_SHADOW_MAX_DISTANCE,
|
||||
rtShadowBias, uint(shadow_ray_cull_mask), lights[i].ml_sourceRadius,
|
||||
rtShadowSampleCount, gl_FragCoord.xy);
|
||||
++shadowedDirectionalCount;
|
||||
} else {
|
||||
shadow = 1.0;
|
||||
@@ -283,6 +306,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;
|
||||
|
||||
@@ -423,7 +450,7 @@ void main()
|
||||
// up to MAX_RT_SHADOW_LIGHTS) inside CalculateLighting()'s loop instead --
|
||||
// this single shared `shadow` value only matters for the CSM fallback below.
|
||||
#ifndef MODEL_SDR_FLAG_RT_SHADOWS
|
||||
shadow = getShadowValue(shadow_map, -vertIn.position.z, vertIn.shadowUV, cascade_distances, smoothness_factors, cascade_offset, cascade_count);
|
||||
shadow = getShadowValue(shadow_map, shadow_map_raw, -vertIn.position.z, vertIn.shadowUV, cascade_distances, smoothness_factors, penumbra_scale, cascade_offset, cascade_count);
|
||||
#endif
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_SHADOWS
|
||||
baseColor.rgb = CalculateLighting(normal, baseColor.rgb, specColor.rgb, glossData, fresnelFactor, shadow, aoFactors.x);
|
||||
@@ -464,7 +491,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 +536,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"
|
||||
@@ -115,6 +125,10 @@ uniform shadowCascadeParams {
|
||||
int cascade_count;
|
||||
float rtShadowBiasMin;
|
||||
float rtShadowBiasMax;
|
||||
int rtShadowSampleCount;
|
||||
int rtaoSampleCount;
|
||||
float rtaoRadius;
|
||||
float rtaoStrength;
|
||||
|
||||
mat4 shadow_mv_matrix;
|
||||
|
||||
@@ -124,6 +138,7 @@ uniform shadowCascadeParams {
|
||||
mat4 shadow_proj_matrix[NUM_SHADOW_CASCADES];
|
||||
vec4 cascade_distances[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
|
||||
vec4 smoothness_factors[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
|
||||
vec4 penumbra_scale[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
|
||||
};
|
||||
|
||||
#ifdef VULKAN
|
||||
@@ -145,6 +160,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 +185,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 +270,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,17 +1,31 @@
|
||||
//? #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
|
||||
|
||||
layout (std140) uniform shadowCascadeParams {
|
||||
int cascade_offset;
|
||||
int cascade_count;
|
||||
float rtShadowBiasMin;
|
||||
float rtShadowBiasMax;
|
||||
// OpenGL-only: the Vulkan backend has no geometry-shader stage, and disables the
|
||||
// shadow pass outright when it can't write gl_Layer from the vertex shader, so it
|
||||
// never requests GEOMETRY_FALLBACK. Hence no Vulkan set/binding qualifier here.
|
||||
layout(std140) uniform shadowCascadeParams {
|
||||
int cascade_offset;
|
||||
int cascade_count;
|
||||
float rtShadowBiasMin;
|
||||
float rtShadowBiasMax;
|
||||
int rtShadowSampleCount;
|
||||
int rtaoSampleCount;
|
||||
float rtaoRadius;
|
||||
float rtaoStrength;
|
||||
|
||||
mat4 shadow_mv_matrix;
|
||||
|
||||
@@ -21,19 +35,29 @@ layout (std140) uniform shadowCascadeParams {
|
||||
mat4 shadow_proj_matrix[NUM_SHADOW_CASCADES];
|
||||
vec4 cascade_distances[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
|
||||
vec4 smoothness_factors[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
|
||||
vec4 penumbra_scale[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
|
||||
};
|
||||
|
||||
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 +73,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;
|
||||
@@ -38,10 +45,14 @@ layout(set = 0, binding = 6, std140)
|
||||
layout(std140)
|
||||
#endif
|
||||
uniform shadowCascadeParams {
|
||||
int cascade_offset;
|
||||
int cascade_count;
|
||||
float rtShadowBiasMin;
|
||||
float rtShadowBiasMax;
|
||||
int cascade_offset;
|
||||
int cascade_count;
|
||||
float rtShadowBiasMin;
|
||||
float rtShadowBiasMax;
|
||||
int rtShadowSampleCount;
|
||||
int rtaoSampleCount;
|
||||
float rtaoRadius;
|
||||
float rtaoStrength;
|
||||
|
||||
mat4 shadow_mv_matrix;
|
||||
|
||||
@@ -51,6 +62,7 @@ uniform shadowCascadeParams {
|
||||
mat4 shadow_proj_matrix[NUM_SHADOW_CASCADES];
|
||||
vec4 cascade_distances[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
|
||||
vec4 smoothness_factors[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
|
||||
vec4 penumbra_scale[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
|
||||
};
|
||||
|
||||
#ifdef VULKAN
|
||||
@@ -66,12 +78,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 +137,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 +158,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;
|
||||
|
||||
@@ -6,33 +6,47 @@
|
||||
#define SAMPLES_PCSS 1
|
||||
#endif
|
||||
|
||||
float samplePoissonPCSS(sampler2DArrayShadow shadow_map, vec4 shadowUV, float maxUVOffset)
|
||||
{
|
||||
// Full 16-tap Poisson disc. Unconditional (independent of SMOOTH_PCSS) because it has three
|
||||
// consumers with different needs: samplePoissonPCSS() takes the first SAMPLES_PCSS of these,
|
||||
// pcssBlockerSearch() always wants 8 (every second tap) regardless of SAMPLES_PCSS (a
|
||||
// blocker search only needs a coarse estimate), and the raytraced cone/AO sampling further
|
||||
// below always
|
||||
// wants the full spread. Keeping one table means none of those three can end up indexing
|
||||
// past a table sized for a different consumer.
|
||||
const vec2 poissonDisc16[16] = vec2[](
|
||||
vec2(-0.76275, -0.3432573),
|
||||
vec2(-0.5226235, -0.8277544),
|
||||
vec2(-0.3780261, 0.01528688),
|
||||
vec2(-0.7742821, 0.4245702),
|
||||
vec2(0.04196143, -0.02622231),
|
||||
vec2(-0.2974772, -0.4722782),
|
||||
vec2(-0.516093, 0.71495),
|
||||
vec2(-0.3257416, 0.3910343),
|
||||
vec2(0.2705966, 0.6670476),
|
||||
vec2(0.4918377, 0.1853267),
|
||||
vec2(0.4428544, -0.6251478),
|
||||
vec2(-0.09204347, 0.9267113),
|
||||
vec2(0.391505, -0.2558275),
|
||||
vec2(0.05605913, -0.7570801),
|
||||
vec2(0.81772, -0.02475523),
|
||||
vec2(0.6890262, 0.5191521)
|
||||
);
|
||||
|
||||
// Low-quality raster PCSS fallback: a single centered sample (i.e. no offset at all) rather
|
||||
// than poissonDisc16[0], which is just one arbitrary tap among sixteen and no better centered
|
||||
// than any other.
|
||||
const vec2 poissonDiscCenter[1] = vec2[](vec2(0.0, 0.0));
|
||||
|
||||
// samplePoissonPCSS()'s tap table: the full disc when taking all SAMPLES_PCSS == 16 of them,
|
||||
// or the single centered sample when SAMPLES_PCSS == 1.
|
||||
#if SMOOTH_PCSS
|
||||
vec2 poissonDisc[16] = vec2[](
|
||||
vec2(-0.76275, -0.3432573),
|
||||
vec2(-0.5226235, -0.8277544),
|
||||
vec2(-0.3780261, 0.01528688),
|
||||
vec2(-0.7742821, 0.4245702),
|
||||
vec2(0.04196143, -0.02622231),
|
||||
vec2(-0.2974772, -0.4722782),
|
||||
vec2(-0.516093, 0.71495),
|
||||
vec2(-0.3257416, 0.3910343),
|
||||
vec2(0.2705966, 0.6670476),
|
||||
vec2(0.4918377, 0.1853267),
|
||||
vec2(0.4428544, -0.6251478),
|
||||
vec2(-0.09204347, 0.9267113),
|
||||
vec2(0.391505, -0.2558275),
|
||||
vec2(0.05605913, -0.7570801),
|
||||
vec2(0.81772, -0.02475523),
|
||||
vec2(0.6890262, 0.5191521)
|
||||
);
|
||||
#define poissonDisc poissonDisc16
|
||||
#else
|
||||
vec2 poissonDisc[1] = vec2[](
|
||||
vec2(0.0,0.0)
|
||||
);
|
||||
#define poissonDisc poissonDiscCenter
|
||||
#endif
|
||||
|
||||
float samplePoissonPCSS(sampler2DArrayShadow shadow_map, vec4 shadowUV, float maxUVOffset)
|
||||
{
|
||||
float visibility = 0.0f;
|
||||
for (int i=0; i<SAMPLES_PCSS; i++) {
|
||||
vec4 uv = shadowUV;
|
||||
@@ -42,8 +56,82 @@ float samplePoissonPCSS(sampler2DArrayShadow shadow_map, vec4 shadowUV, float ma
|
||||
return visibility * (1.0f/float(SAMPLES_PCSS));
|
||||
}
|
||||
|
||||
float getShadowValue(sampler2DArrayShadow shadow_map, float depth, vec4 shadowUV[NUM_SHADOW_CASCADES],
|
||||
// PCSS blocker search: raw (non-compare) depth taps within searchRadiusUV, using every
|
||||
// second tap of poissonDisc16. The first 8 taps are mostly on one side (x < 0), so they
|
||||
// would search the penumbra on one side only. This is always the full-spread table, not
|
||||
// samplePoissonPCSS()'s poissonDisc --
|
||||
// a blocker search only needs a coarse estimate, not the final filtered result, but it needs
|
||||
// that estimate regardless of how many taps the raster PCSS path itself is set to take).
|
||||
// "Blocker" = stored < receiverDepth, matching the GL_LEQUAL/eLessOrEqual compare convention
|
||||
// the hardware PCF sampler already uses (visible iff Dref <= stored) -- see the compare-sampler
|
||||
// setup in gropengltnl.cpp / VulkanPostProcessingShadow.cpp.
|
||||
// A negative return means "no blockers found", which the average blocker depth (always >= 0)
|
||||
// can't otherwise be distinguished from "found some at depth 0".
|
||||
float pcssBlockerSearch(sampler2DArray shadow_map_raw, vec4 shadowUV, float searchRadiusUV)
|
||||
{
|
||||
float sum = 0.0;
|
||||
int blockerCount = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
vec2 uv = shadowUV.xy + poissonDisc16[i * 2] * searchRadiusUV;
|
||||
float sampled = texture(shadow_map_raw, vec3(uv, shadowUV.z)).r;
|
||||
if (sampled < shadowUV.w) {
|
||||
sum += sampled;
|
||||
blockerCount++;
|
||||
}
|
||||
}
|
||||
return blockerCount > 0 ? (sum / float(blockerCount)) : -1.0;
|
||||
}
|
||||
|
||||
// Computes the PCSS penumbra radius for one cascade sample: a blocker search followed by
|
||||
// the orthographic-light penumbra formula (see shadow_smoothness_scale()'s doc comment in
|
||||
// shadows.cpp for the derivation), clamped to [1 texel, smoothnessCeiling]. penumbraScale is
|
||||
// Shadow_penumbra_scale[cascade] -- a negative value is the "unsupported or user-disabled"
|
||||
// sentinel (see shadow_contact_hardening_enabled() in shadows.cpp), in which case this
|
||||
// returns smoothnessCeiling immediately: bit-identical to the fixed-width behavior from
|
||||
// before contact hardening existed, and skips the blocker search's extra texture reads.
|
||||
float pcssPenumbraRadius(sampler2DArrayShadow shadow_map, sampler2DArray shadow_map_raw,
|
||||
vec4 shadowUV, float penumbraScale, float smoothnessCeiling)
|
||||
{
|
||||
float texelFloor = 1.0 / float(textureSize(shadow_map, 0).x);
|
||||
|
||||
if (penumbraScale < 0.0) {
|
||||
return smoothnessCeiling;
|
||||
}
|
||||
|
||||
float avgBlockerDepth = pcssBlockerSearch(shadow_map_raw, shadowUV, smoothnessCeiling);
|
||||
if (avgBlockerDepth < 0.0) {
|
||||
// The sparse 8-tap search found nothing, but that doesn't prove there's no thin
|
||||
// occluder nearby -- run the full PCF at the hardest radius rather than assuming
|
||||
// fully lit, so a missed thin blocker still reads as an edge instead of popping to
|
||||
// no shadow at all.
|
||||
return texelFloor;
|
||||
}
|
||||
|
||||
float deltaDepth = max(shadowUV.w - avgBlockerDepth, 0.0);
|
||||
return clamp(deltaDepth * penumbraScale, texelFloor, smoothnessCeiling);
|
||||
}
|
||||
|
||||
// PCSS-filtered shadow visibility for a single cascade: looks up its penumbra radius (see
|
||||
// pcssPenumbraRadius()) and samples the shadow map at that radius. Factored out of
|
||||
// getShadowValue() below so the cascade-index unpacking (v/i from cascade/4, cascade%4) and
|
||||
// the penumbra-radius lookup each appear once, whichever of getShadowValue()'s two return
|
||||
// paths runs.
|
||||
float shadowVisibilityForCascade(sampler2DArrayShadow shadow_map, sampler2DArray shadow_map_raw,
|
||||
vec4 shadowUV, int cascade, vec4 smoothness_factors[(NUM_SHADOW_CASCADES + 4 - 1) / 4],
|
||||
vec4 penumbra_scale[(NUM_SHADOW_CASCADES + 4 - 1) / 4])
|
||||
{
|
||||
int v = cascade / 4;
|
||||
int i = cascade % 4;
|
||||
|
||||
float radius = pcssPenumbraRadius(shadow_map, shadow_map_raw, shadowUV,
|
||||
penumbra_scale[v][i], smoothness_factors[v][i]);
|
||||
|
||||
return samplePoissonPCSS(shadow_map, shadowUV, radius);
|
||||
}
|
||||
|
||||
float getShadowValue(sampler2DArrayShadow shadow_map, sampler2DArray shadow_map_raw, float depth, vec4 shadowUV[NUM_SHADOW_CASCADES],
|
||||
vec4 cascade_distances[(NUM_SHADOW_CASCADES + 4 - 1) / 4], vec4 smoothness_factors[(NUM_SHADOW_CASCADES + 4 - 1) / 4],
|
||||
vec4 penumbra_scale[(NUM_SHADOW_CASCADES + 4 - 1) / 4],
|
||||
int cascade_offset, int cascade_count)
|
||||
{
|
||||
int cascade = cascade_offset + cascade_count;
|
||||
@@ -52,19 +140,19 @@ float getShadowValue(sampler2DArrayShadow shadow_map, float depth, vec4 shadowUV
|
||||
}
|
||||
if (cascade >= cascade_offset + cascade_count || cascade < cascade_offset) return 1.0;
|
||||
|
||||
int cascade_v = cascade / 4;
|
||||
int cascade_i = cascade % 4;
|
||||
|
||||
float cascade_start = (cascade > cascade_offset) ? cascade_distances[(cascade - 1) / 4][(cascade - 1) % 4] : 0.0;
|
||||
float cascade_end = cascade_distances[cascade_v][cascade_i];
|
||||
float cascade_end = cascade_distances[cascade / 4][cascade % 4];
|
||||
float dist_threshold = (cascade_end - cascade_start) * 0.2;
|
||||
|
||||
if (cascade_end - dist_threshold > depth || cascade >= cascade_offset + cascade_count - 1)
|
||||
return samplePoissonPCSS(shadow_map, shadowUV[cascade], smoothness_factors[cascade_v][cascade_i]);
|
||||
return shadowVisibilityForCascade(shadow_map, shadow_map_raw, shadowUV[cascade], cascade,
|
||||
smoothness_factors, penumbra_scale);
|
||||
|
||||
return mix(
|
||||
samplePoissonPCSS(shadow_map, shadowUV[cascade], smoothness_factors[cascade_v][cascade_i]),
|
||||
samplePoissonPCSS(shadow_map, shadowUV[cascade + 1], smoothness_factors[(cascade + 1) / 4][(cascade + 1) % 4]),
|
||||
shadowVisibilityForCascade(shadow_map, shadow_map_raw, shadowUV[cascade], cascade,
|
||||
smoothness_factors, penumbra_scale),
|
||||
shadowVisibilityForCascade(shadow_map, shadow_map_raw, shadowUV[cascade + 1], cascade + 1,
|
||||
smoothness_factors, penumbra_scale),
|
||||
smoothstep(cascade_end - dist_threshold, cascade_end, depth));
|
||||
}
|
||||
// Raytraced shadow test via inline ray query against the shadow TLAS. Only
|
||||
@@ -79,7 +167,7 @@ float getShadowValue(sampler2DArrayShadow shadow_map, float depth, vec4 shadowUV
|
||||
// in deferred-f.sdr/main-f.sdr references it regardless of which shadow method is active.
|
||||
const float RT_SHADOW_MAX_DISTANCE = 10000.0;
|
||||
|
||||
#if defined(MODEL_SDR_FLAG_RT_SHADOWS) || defined(RT_SHADOWS)
|
||||
#if defined(MODEL_SDR_FLAG_RT_SHADOWS) || defined(RT_SHADOWS) || defined(RTAO)
|
||||
#extension GL_EXT_ray_query : require
|
||||
|
||||
// Ray origin bias grows with distance from the camera, between the rtShadowBiasMin/
|
||||
@@ -132,6 +220,141 @@ float traceShadowRay(accelerationStructureEXT tlas, vec3 worldPos, vec3 worldNor
|
||||
|
||||
return (rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionNoneEXT) ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
// Penumbra/AO sampling: at most this many rays per fragment per light, matching the size of
|
||||
// poissonDisc16 above (the RT cone/AO sampling below always wants the full 16-tap spread,
|
||||
// independent of SAMPLES_PCSS/SMOOTH_PCSS -- see the comment on poissonDisc16). rtShadowSampleCount/
|
||||
// rtaoSampleCount (shadowCascadeParams) are clamped against it, so a bad uniform value can't
|
||||
// index out of the pattern.
|
||||
#define RT_SHADOW_MAX_SAMPLES 16
|
||||
|
||||
// Orthonormal basis spanning the plane perpendicular to axis (a unit vector).
|
||||
void rtBuildOnb(vec3 axis, out vec3 tangent, out vec3 bitangent)
|
||||
{
|
||||
vec3 up = (abs(axis.z) < 0.999) ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
|
||||
tangent = normalize(cross(up, axis));
|
||||
bitangent = cross(axis, tangent);
|
||||
}
|
||||
|
||||
// Interleaved gradient noise (Jimenez 2014) -> per-fragment [0,1) value used to
|
||||
// rotate the shared disc pattern. Callers pass gl_FragCoord.xy as the seed; it is
|
||||
// a parameter rather than read here because this file is also compiled in vertex
|
||||
// stages, where gl_FragCoord doesn't exist.
|
||||
float rtInterleavedGradientNoise(vec2 seed)
|
||||
{
|
||||
return fract(52.9829189 * fract(dot(seed, vec2(0.06711056, 0.00583715))));
|
||||
}
|
||||
|
||||
// Soft (penumbra) variant of traceShadowRay: averages up to sampleCount occlusion
|
||||
// rays whose directions are jittered inside a cone of half-angle
|
||||
// atan(coneHalfAngleTan) around worldLightDir. Callers derive coneHalfAngleTan
|
||||
// from the light's source radius -- for directional lights the light's
|
||||
// source_radius IS the tangent of its angular radius (see $SunAngularSize in
|
||||
// stars.tbl / starfield.cpp), for local lights it's sourceRadius / lightDist --
|
||||
// so penumbras widen with occluder distance like a real area light's would.
|
||||
//
|
||||
// cullMask has the same meaning as in traceShadowRay().
|
||||
//
|
||||
// noiseSeed decorrelates the sample pattern between fragments (see
|
||||
// rtInterleavedGradientNoise() for why it's a parameter). There is no temporal
|
||||
// accumulation in the engine, so the penumbra must fully resolve in this one
|
||||
// pass: the disc pattern is fixed and only its per-fragment rotation is
|
||||
// randomized, trading a little visible grain for stable, flicker-free output.
|
||||
//
|
||||
// sampleCount <= 1 or coneHalfAngleTan <= 0.0 falls back to the single hard ray,
|
||||
// bit-identical to traceShadowRay() -- that keeps the pre-penumbra look and cost
|
||||
// as the default (mods opt in via source radii, users via the sample option).
|
||||
float traceShadowRayCone(accelerationStructureEXT tlas, vec3 worldPos, vec3 worldNormal, vec3 worldLightDir,
|
||||
float tMax, float bias, uint cullMask, float coneHalfAngleTan, int sampleCount, vec2 noiseSeed)
|
||||
{
|
||||
if (sampleCount <= 1 || coneHalfAngleTan <= 0.0) {
|
||||
return traceShadowRay(tlas, worldPos, worldNormal, worldLightDir, tMax, bias, cullMask);
|
||||
}
|
||||
|
||||
vec3 origin = worldPos + worldNormal * bias;
|
||||
vec3 direction = normalize(worldLightDir);
|
||||
|
||||
vec3 tangent, bitangent;
|
||||
rtBuildOnb(direction, tangent, bitangent);
|
||||
|
||||
float rotAngle = rtInterleavedGradientNoise(noiseSeed) * 6.2831853;
|
||||
float cosR = cos(rotAngle);
|
||||
float sinR = sin(rotAngle);
|
||||
|
||||
int samples = min(sampleCount, RT_SHADOW_MAX_SAMPLES);
|
||||
float visibility = 0.0;
|
||||
for (int i = 0; i < samples; ++i) {
|
||||
vec2 p = poissonDisc16[i];
|
||||
p = vec2(p.x * cosR - p.y * sinR, p.x * sinR + p.y * cosR);
|
||||
vec3 sampleDir = normalize(direction + (tangent * p.x + bitangent * p.y) * coneHalfAngleTan);
|
||||
|
||||
rayQueryEXT rq;
|
||||
rayQueryInitializeEXT(rq, tlas, gl_RayFlagsOpaqueEXT | gl_RayFlagsTerminateOnFirstHitEXT,
|
||||
cullMask, origin, 0.001, sampleDir, tMax);
|
||||
while (rayQueryProceedEXT(rq)) {}
|
||||
|
||||
if (rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionNoneEXT) {
|
||||
visibility += 1.0;
|
||||
}
|
||||
}
|
||||
return visibility / float(samples);
|
||||
}
|
||||
|
||||
// Raytraced ambient occlusion: averages up to sampleCount occlusion rays of
|
||||
// length `radius` over the hemisphere around worldNormal and returns ambient
|
||||
// visibility in [0,1] (1 = fully open), shaped by pow(visibility, strength).
|
||||
// Mapping the shared disc pattern up onto the hemisphere (z = sqrt(1-|p|^2))
|
||||
// makes the samples cosine-weighted, matching the diffuse ambient term being
|
||||
// occluded. radius/strength come from the mod's lighting profile ($RTAO Radius /
|
||||
// $RTAO Strength); scene scale varies too much (fighters vs. capships) for a
|
||||
// built-in constant.
|
||||
//
|
||||
// Hits accumulate with a (1 - t/radius) falloff so geometry entering the radius
|
||||
// occludes gradually instead of popping. That needs the *closest* hit's t, so
|
||||
// these rays deliberately omit gl_RayFlagsTerminateOnFirstHitEXT (any-hit t
|
||||
// would make the falloff arbitrary); the extra traversal cost is small because
|
||||
// tMax = radius keeps the rays short.
|
||||
//
|
||||
// sampleCount < 1, radius <= 0, or strength <= 0 returns 1.0 (no occlusion) --
|
||||
// callers gate on the RTAO shader variant, this just keeps bad uniforms benign.
|
||||
float traceAmbientOcclusion(accelerationStructureEXT tlas, vec3 worldPos, vec3 worldNormal,
|
||||
float radius, float strength, int sampleCount, float bias, vec2 noiseSeed)
|
||||
{
|
||||
if (sampleCount < 1 || radius <= 0.0 || strength <= 0.0) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
vec3 origin = worldPos + worldNormal * bias;
|
||||
|
||||
vec3 tangent, bitangent;
|
||||
rtBuildOnb(worldNormal, tangent, bitangent);
|
||||
|
||||
float rotAngle = rtInterleavedGradientNoise(noiseSeed) * 6.2831853;
|
||||
float cosR = cos(rotAngle);
|
||||
float sinR = sin(rotAngle);
|
||||
|
||||
int samples = min(sampleCount, RT_SHADOW_MAX_SAMPLES);
|
||||
float occlusion = 0.0;
|
||||
for (int i = 0; i < samples; ++i) {
|
||||
vec2 p = poissonDisc16[i];
|
||||
p = vec2(p.x * cosR - p.y * sinR, p.x * sinR + p.y * cosR);
|
||||
vec3 sampleDir = normalize(tangent * p.x + bitangent * p.y
|
||||
+ worldNormal * sqrt(max(1.0 - dot(p, p), 0.0)));
|
||||
|
||||
rayQueryEXT rq;
|
||||
rayQueryInitializeEXT(rq, tlas, gl_RayFlagsOpaqueEXT,
|
||||
0xFF, origin, 0.001, sampleDir, radius);
|
||||
while (rayQueryProceedEXT(rq)) {}
|
||||
|
||||
if (rayQueryGetIntersectionTypeEXT(rq, true) != gl_RayQueryCommittedIntersectionNoneEXT) {
|
||||
float hitT = rayQueryGetIntersectionTEXT(rq, true);
|
||||
occlusion += 1.0 - clamp(hitT / radius, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
float visibility = clamp(1.0 - occlusion / float(samples), 0.0, 1.0);
|
||||
return pow(visibility, strength);
|
||||
}
|
||||
#endif
|
||||
|
||||
vec4 transformToShadowMap(mat4 shadow_proj_matrix, int i, vec4 pos)
|
||||
|
||||
@@ -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!
|
||||
|
||||
+108
-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"
|
||||
@@ -83,6 +85,7 @@ gr_capability_def gr_capabilities[] = {
|
||||
GR_CAPABILITY_ENTRY(INSTANCED_RENDERING),
|
||||
GR_CAPABILITY_ENTRY(FAST_SHADOWS),
|
||||
GR_CAPABILITY_ENTRY(RAYTRACED_SHADOWS),
|
||||
GR_CAPABILITY_ENTRY(SHADOW_CONTACT_HARDENING),
|
||||
};
|
||||
|
||||
const size_t gr_capabilities_num = sizeof(gr_capabilities) / sizeof(gr_capabilities[0]);
|
||||
@@ -289,7 +292,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)
|
||||
{
|
||||
@@ -920,6 +927,29 @@ bool gr_is_smaa_mode(AntiAliasMode mode) {
|
||||
return mode == AntiAliasMode::SMAA_Low || mode == AntiAliasMode::SMAA_Medium || mode == AntiAliasMode::SMAA_High || mode == AntiAliasMode::SMAA_Ultra;
|
||||
}
|
||||
|
||||
SCP_vector<float> gr_get_supported_anisotropy_levels()
|
||||
{
|
||||
float max;
|
||||
if (!gr_get_property(gr_property::MAX_ANISOTROPY, &max)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (max <= 2.0f) {
|
||||
return {};
|
||||
}
|
||||
|
||||
SCP_vector<float> out;
|
||||
|
||||
// We assume here that the anisotropy levels are powers of two...
|
||||
float current = 1.0f;
|
||||
while (current <= max) {
|
||||
out.push_back(current);
|
||||
current *= 2.0f;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static void parse_post_processing_func()
|
||||
{
|
||||
bool value;
|
||||
@@ -1703,6 +1733,13 @@ void gr_screen_resize(int width, int height)
|
||||
gr_screen.save_max_h_unscaled_zoomed = gr_screen.max_h_unscaled_zoomed;
|
||||
|
||||
gr_setup_viewport();
|
||||
|
||||
// The offscreen targets that back the scene/post-processing pipeline were sized for the old
|
||||
// gr_screen; give the backend a chance to grow them before anything renders at the new size.
|
||||
// Backends that already handle this elsewhere (Vulkan, via recreateSwapChain()) leave it unset.
|
||||
if (gr_screen.gf_resize_render_targets) {
|
||||
gr_screen.gf_resize_render_targets();
|
||||
}
|
||||
}
|
||||
|
||||
void gr_window_to_render_pos(float& x, float& y)
|
||||
@@ -3259,24 +3296,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 +3345,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 +3382,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 +3657,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) {
|
||||
|
||||
+104
-1
@@ -262,6 +262,7 @@ enum shader_type {
|
||||
|
||||
#define SDR_FLAG_ENV_MAP (1 << 0)
|
||||
#define SDR_FLAG_DEFERRED_RT_SHADOWS (1 << 1)
|
||||
#define SDR_FLAG_DEFERRED_RTAO (1 << 2)
|
||||
|
||||
#define SDR_FLAG_SHADOW_FALLBACK (1 << 0)
|
||||
|
||||
@@ -370,7 +371,12 @@ enum class gr_capability {
|
||||
CAPABILITY_INSTANCED_RENDERING,
|
||||
CAPABILITY_FAST_SHADOWS,
|
||||
CAPABILITY_QUERIES_REUSABLE,
|
||||
CAPABILITY_RAYTRACED_SHADOWS
|
||||
CAPABILITY_RAYTRACED_SHADOWS,
|
||||
// A second, non-compare sampler bound to the shadow map for raw depth reads, needed by
|
||||
// the shadow-map PCSS blocker search. Vulkan always has this (samplers are independent
|
||||
// of images there); OpenGL needs GL 3.3 (glGenSamplers/glBindSampler), since the compare mode is
|
||||
// otherwise texture-object state shared by every sampler bound to that texture.
|
||||
CAPABILITY_SHADOW_CONTACT_HARDENING
|
||||
};
|
||||
|
||||
struct gr_capability_def {
|
||||
@@ -690,6 +696,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;
|
||||
@@ -861,6 +917,12 @@ typedef struct screen {
|
||||
std::function<void()> gf_scene_texture_end;
|
||||
std::function<void()> gf_copy_effect_texture;
|
||||
|
||||
// Grow the offscreen render targets that back the scene/post-processing pipeline to cover the
|
||||
// current gr_screen, if they don't already. Called from gr_screen_resize(). Optional: the
|
||||
// Vulkan backend leaves this unset because VulkanRenderer::recreateSwapChain() already owns
|
||||
// resizing its extent-sized targets.
|
||||
std::function<void()> gf_resize_render_targets;
|
||||
|
||||
std::function<void(int zbias)> gf_zbias;
|
||||
|
||||
std::function<void(int)> gf_set_fill_mode;
|
||||
@@ -963,6 +1025,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 +1231,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 +1405,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,
|
||||
@@ -1411,6 +1508,12 @@ inline bool gr_get_property(gr_property property, void* destination)
|
||||
return gr_screen.gf_get_property(property, destination);
|
||||
}
|
||||
|
||||
// Anisotropic filtering levels the current hardware supports: 1.0 (off), then powers of two up to
|
||||
// the reported maximum. Empty if anisotropy is unavailable or the hardware caps out below 4x, in
|
||||
// which case there is nothing meaningful to offer. Backs both the in-game option's enumerator and
|
||||
// qtFRED's Preferences combo, so the two can't drift.
|
||||
SCP_vector<float> gr_get_supported_anisotropy_levels();
|
||||
|
||||
inline void gr_push_debug_group(const char* name)
|
||||
{
|
||||
gr_screen.gf_push_debug_group(name);
|
||||
|
||||
@@ -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
|
||||
@@ -1126,6 +1126,7 @@ void gr_opengl_init_function_pointers()
|
||||
gr_screen.gf_scene_texture_begin = gr_opengl_scene_texture_begin;
|
||||
gr_screen.gf_scene_texture_end = gr_opengl_scene_texture_end;
|
||||
gr_screen.gf_copy_effect_texture = gr_opengl_copy_effect_texture;
|
||||
gr_screen.gf_resize_render_targets = gr_opengl_resize_render_targets;
|
||||
|
||||
gr_screen.gf_deferred_lighting_begin = gr_opengl_deferred_lighting_begin;
|
||||
gr_screen.gf_deferred_lighting_msaa = gr_opengl_deferred_lighting_msaa;
|
||||
@@ -1164,6 +1165,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 +1396,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 +1455,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")));
|
||||
@@ -1513,7 +1517,7 @@ bool gr_opengl_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps)
|
||||
opengl_shader_init();
|
||||
|
||||
// post processing effects, after shaders are initialized
|
||||
opengl_setup_scene_textures();
|
||||
opengl_setup_scene_textures(gr_screen.max_w, gr_screen.max_h);
|
||||
opengl_post_process_init();
|
||||
|
||||
// must be called after extensions are setup
|
||||
@@ -1628,6 +1632,18 @@ bool gr_opengl_is_capable(gr_capability capability)
|
||||
case gr_capability::CAPABILITY_RAYTRACED_SHADOWS:
|
||||
// Raytraced shadows are only implemented for the Vulkan backend.
|
||||
return false;
|
||||
case gr_capability::CAPABILITY_SHADOW_CONTACT_HARDENING:
|
||||
// Needs a second sampler object (compare mode off) on the shadow map. Sampler
|
||||
// objects are technically also available pre-3.3 via ARB_sampler_objects, but this
|
||||
// project's glad build only wires glGenSamplers/glBindSampler/glSamplerParameteri
|
||||
// up behind the core GL_VERSION_3_3 flag, not a separate ARB entry point -- so that's
|
||||
// the check that actually reflects whether those functions are non-null here.
|
||||
#ifdef USE_OPENGL_ES
|
||||
// GLES has no GLAD_GL_VERSION_3_3 symbol, and the sampler setup here is desktop GL only.
|
||||
return false;
|
||||
#else
|
||||
return GLAD_GL_VERSION_3_3 != 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ void gr_opengl_deferred_lighting_begin(bool clearNonColorBufs)
|
||||
Current_shader->program->Uniforms.setTextureUniform("tex", 0);
|
||||
GL_state.SetAlphaBlendMode(gr_alpha_blend::ALPHA_BLEND_NONE);
|
||||
GL_state.SetZbufferType(ZBUFFER_TYPE_NONE);
|
||||
opengl_draw_full_screen_textured(0, 0, 1, 1);
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
} else {
|
||||
// Copy the existing color data into the emissive part of the G-buffer since everything that already existed is
|
||||
// treated as emissive
|
||||
@@ -159,7 +159,9 @@ void gr_opengl_deferred_lighting_msaa()
|
||||
});
|
||||
GL_state.SetAlphaBlendMode(gr_alpha_blend::ALPHA_BLEND_NONE);
|
||||
GL_state.SetZbufferType(ZBUFFER_TYPE_WRITE);
|
||||
opengl_draw_full_screen_textured(0, 0, 1, 1);
|
||||
// msaa-f.sdr resolves via ivec2(textureSize(texColor) * fragTexCoord), so the texcoords have to
|
||||
// stay inside the rendered sub-rectangle of the multisampled G-buffer.
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
}
|
||||
|
||||
void gr_opengl_deferred_lighting_end()
|
||||
@@ -174,6 +176,11 @@ void gr_opengl_deferred_lighting_end()
|
||||
glDrawBuffer(GL_COLOR_ATTACHMENT0);
|
||||
|
||||
GL_state.ColorMask(true, true, true, false);
|
||||
|
||||
// Do not leave the raw-depth sampler bound after the pass.
|
||||
if (Shadow_map_raw_sampler) {
|
||||
GL_state.Texture.BindSampler(11, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static GLuint deferred_light_cylinder_vbo = 0;
|
||||
@@ -253,6 +260,10 @@ void gr_opengl_deferred_lighting_finish()
|
||||
GL_state.Texture.Enable(3, GL_TEXTURE_2D, Scene_specular_texture);
|
||||
if (Shadow_quality != ShadowQuality::Disabled) {
|
||||
GL_state.Texture.Enable(4, GL_TEXTURE_2D_ARRAY, Shadow_map_depth_texture);
|
||||
if (Shadow_map_raw_sampler) {
|
||||
GL_state.Texture.Enable(11, GL_TEXTURE_2D_ARRAY, Shadow_map_depth_texture);
|
||||
GL_state.Texture.BindSampler(11, Shadow_map_raw_sampler);
|
||||
}
|
||||
}
|
||||
|
||||
if (ENVMAP > 0) {
|
||||
@@ -319,8 +330,12 @@ void gr_opengl_deferred_lighting_finish()
|
||||
shadow_cascade_params_bind_deferred();
|
||||
}
|
||||
|
||||
header->invScreenWidth = 1.0f / gr_screen.max_w;
|
||||
header->invScreenHeight = 1.0f / gr_screen.max_h;
|
||||
// deferred-f.sdr turns gl_FragCoord into a G-buffer texture coordinate with these, so they
|
||||
// have to normalize against the G-buffer's own dimensions. Those only equal gr_screen while
|
||||
// the viewport exactly fills the scene textures -- not after a shrink, and not when the
|
||||
// allocation was clamped by GL_max_renderbuffer_size.
|
||||
header->invScreenWidth = 1.0f / Scene_texture_width;
|
||||
header->invScreenHeight = 1.0f / Scene_texture_height;
|
||||
header->nearPlane = gr_near_plane;
|
||||
|
||||
{
|
||||
@@ -555,7 +570,8 @@ void gr_opengl_deferred_lighting_finish()
|
||||
data->clip_dist = Neb2_fog_clip_distance;
|
||||
});
|
||||
|
||||
opengl_draw_full_screen_textured(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
// fog-f.sdr samples the composite and depth targets straight off fragTexCoord.
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
|
||||
if (bDrawNebVolumetrics) {
|
||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||
@@ -651,6 +667,12 @@ void gr_opengl_deferred_lighting_finish()
|
||||
|
||||
{
|
||||
GR_DEBUG_SCOPE("Volumetric Nebulae Draw");
|
||||
// Deliberately unscaled. volumetric-f.sdr uses fragTexCoord for two incompatible
|
||||
// things: reconstructing an eye-space ray direction, which needs the full 0..1 range
|
||||
// across the viewport, and sampling composite/depth/emissive, which needs the
|
||||
// rendered sub-rectangle. Scaling here would fix the sampling and skew every ray.
|
||||
// Separating the two needs a second varying (or a scale uniform) in the shader; until
|
||||
// then volumetrics are only correct while the targets exactly match the viewport.
|
||||
opengl_draw_full_screen_textured(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
}
|
||||
GL_state.Texture.Enable(Scene_emissive_texture);
|
||||
|
||||
@@ -70,6 +70,33 @@ int Scene_texture_height;
|
||||
GLfloat Scene_texture_u_scale = 1.0f;
|
||||
GLfloat Scene_texture_v_scale = 1.0f;
|
||||
|
||||
// Render targets are torn down and rebuilt mid-session by gr_opengl_resize_render_targets(), not
|
||||
// just at shutdown, so deletion has to go through the state cache: the driver is free to hand a
|
||||
// freed name straight back out, and a cache entry still holding that name would make a later
|
||||
// Enable() of the recycled texture a no-op.
|
||||
void opengl_delete_render_texture(GLuint& tex)
|
||||
{
|
||||
if ( !tex ) {
|
||||
return;
|
||||
}
|
||||
|
||||
GL_state.Texture.Delete(tex);
|
||||
glDeleteTextures(1, &tex);
|
||||
tex = 0;
|
||||
}
|
||||
|
||||
// Callers must have bound something else first (the resize path binds 0); the framebuffer cache
|
||||
// has no equivalent of Texture.Delete() to unbind through.
|
||||
void opengl_delete_render_framebuffer(GLuint& fbo)
|
||||
{
|
||||
if ( !fbo ) {
|
||||
return;
|
||||
}
|
||||
|
||||
glDeleteFramebuffers(1, &fbo);
|
||||
fbo = 0;
|
||||
}
|
||||
|
||||
inline GLenum opengl_primitive_type(primitive_type prim_type)
|
||||
{
|
||||
switch ( prim_type ) {
|
||||
@@ -98,7 +125,7 @@ void gr_opengl_sphere(material* material_def, float /*rad*/)
|
||||
}
|
||||
|
||||
extern int opengl_check_framebuffer();
|
||||
void opengl_setup_scene_textures()
|
||||
void opengl_setup_scene_textures(int width, int height)
|
||||
{
|
||||
Scene_texture_initialized = 0;
|
||||
|
||||
@@ -113,10 +140,10 @@ void opengl_setup_scene_textures()
|
||||
return;
|
||||
}
|
||||
|
||||
// clamp size, if needed
|
||||
Scene_texture_width = gr_screen.max_w;
|
||||
Scene_texture_height = gr_screen.max_h;
|
||||
Scene_texture_width = width;
|
||||
Scene_texture_height = height;
|
||||
|
||||
// clamp size, if needed
|
||||
if ( Scene_texture_width > GL_max_renderbuffer_size ) {
|
||||
Scene_texture_width = GL_max_renderbuffer_size;
|
||||
}
|
||||
@@ -125,6 +152,13 @@ void opengl_setup_scene_textures()
|
||||
Scene_texture_height = GL_max_renderbuffer_size;
|
||||
}
|
||||
|
||||
mprintf((" Scene textures: %dx%d (screen %dx%d, max renderbuffer %d)\n",
|
||||
Scene_texture_width,
|
||||
Scene_texture_height,
|
||||
gr_screen.max_w,
|
||||
gr_screen.max_h,
|
||||
GL_max_renderbuffer_size));
|
||||
|
||||
// create framebuffer
|
||||
glGenFramebuffers(1, &Scene_framebuffer);
|
||||
GL_state.BindFrameBuffer(Scene_framebuffer);
|
||||
@@ -333,32 +367,15 @@ void opengl_setup_scene_textures()
|
||||
|
||||
if ( opengl_check_framebuffer() ) {
|
||||
GL_state.BindFrameBuffer(0);
|
||||
glDeleteFramebuffers(1, &Scene_framebuffer);
|
||||
Scene_framebuffer = 0;
|
||||
opengl_delete_render_framebuffer(Scene_framebuffer);
|
||||
|
||||
glDeleteTextures(1, &Scene_color_texture);
|
||||
Scene_color_texture = 0;
|
||||
|
||||
glDeleteTextures(1, &Scene_position_texture);
|
||||
Scene_position_texture = 0;
|
||||
|
||||
glDeleteTextures(1, &Scene_normal_texture);
|
||||
Scene_normal_texture = 0;
|
||||
|
||||
glDeleteTextures(1, &Scene_specular_texture);
|
||||
Scene_specular_texture = 0;
|
||||
|
||||
glDeleteTextures(1, &Scene_emissive_texture);
|
||||
Scene_emissive_texture = 0;
|
||||
|
||||
glDeleteTextures(1, &Scene_depth_texture);
|
||||
Scene_depth_texture = 0;
|
||||
|
||||
glDeleteTextures(1, &Scene_luminance_texture);
|
||||
Scene_luminance_texture = 0;
|
||||
|
||||
//glDeleteTextures(1, &Scene_fxaa_output_texture);
|
||||
//Scene_fxaa_output_texture = 0;
|
||||
opengl_delete_render_texture(Scene_color_texture);
|
||||
opengl_delete_render_texture(Scene_position_texture);
|
||||
opengl_delete_render_texture(Scene_normal_texture);
|
||||
opengl_delete_render_texture(Scene_specular_texture);
|
||||
opengl_delete_render_texture(Scene_emissive_texture);
|
||||
opengl_delete_render_texture(Scene_depth_texture);
|
||||
opengl_delete_render_texture(Scene_luminance_texture);
|
||||
|
||||
Gr_post_processing_enabled = false;
|
||||
Gr_enable_soft_particles = false;
|
||||
@@ -687,79 +704,165 @@ void opengl_scene_texture_shutdown()
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Scene_color_texture ) {
|
||||
glDeleteTextures(1, &Scene_color_texture);
|
||||
Scene_color_texture = 0;
|
||||
}
|
||||
// Everything opengl_setup_scene_textures() generated, in the same order. Note that
|
||||
// GammaBlit_texture is 0 when the gamma pass is aliasing Scene_ldr_texture, so the shared
|
||||
// texture is only released once.
|
||||
opengl_delete_render_texture(Scene_color_texture);
|
||||
opengl_delete_render_texture(Scene_ldr_texture);
|
||||
opengl_delete_render_texture(Scene_position_texture);
|
||||
opengl_delete_render_texture(Scene_normal_texture);
|
||||
opengl_delete_render_texture(Scene_specular_texture);
|
||||
opengl_delete_render_texture(Scene_emissive_texture);
|
||||
opengl_delete_render_texture(Scene_composite_texture);
|
||||
opengl_delete_render_texture(Scene_luminance_texture);
|
||||
opengl_delete_render_texture(Cockpit_depth_texture);
|
||||
opengl_delete_render_texture(Scene_depth_texture);
|
||||
opengl_delete_render_framebuffer(Scene_framebuffer);
|
||||
|
||||
if ( Scene_position_texture ) {
|
||||
glDeleteTextures(1, &Scene_position_texture);
|
||||
Scene_position_texture = 0;
|
||||
}
|
||||
opengl_delete_render_texture(Scene_color_texture_ms);
|
||||
opengl_delete_render_texture(Scene_position_texture_ms);
|
||||
opengl_delete_render_texture(Scene_normal_texture_ms);
|
||||
opengl_delete_render_texture(Scene_specular_texture_ms);
|
||||
opengl_delete_render_texture(Scene_emissive_texture_ms);
|
||||
opengl_delete_render_texture(Scene_depth_texture_ms);
|
||||
opengl_delete_render_framebuffer(Scene_framebuffer_ms);
|
||||
|
||||
if ( Scene_normal_texture ) {
|
||||
glDeleteTextures(1, &Scene_normal_texture);
|
||||
Scene_normal_texture = 0;
|
||||
}
|
||||
opengl_delete_render_texture(Back_texture);
|
||||
opengl_delete_render_texture(Back_depth_texture);
|
||||
opengl_delete_render_framebuffer(Back_framebuffer);
|
||||
|
||||
if ( Scene_specular_texture ) {
|
||||
glDeleteTextures(1, &Scene_specular_texture);
|
||||
Scene_specular_texture = 0;
|
||||
}
|
||||
opengl_delete_render_texture(GammaBlit_texture);
|
||||
opengl_delete_render_framebuffer(GammaBlit_framebuffer);
|
||||
|
||||
if (Scene_emissive_texture) {
|
||||
glDeleteTextures(1, &Scene_emissive_texture);
|
||||
Scene_emissive_texture = 0;
|
||||
}
|
||||
|
||||
if ( Scene_depth_texture ) {
|
||||
glDeleteTextures(1, &Scene_depth_texture);
|
||||
Scene_depth_texture = 0;
|
||||
}
|
||||
|
||||
if ( Scene_framebuffer ) {
|
||||
glDeleteFramebuffers(1, &Scene_framebuffer);
|
||||
Scene_framebuffer = 0;
|
||||
}
|
||||
|
||||
if (Back_texture) {
|
||||
glDeleteTextures(1, &Back_texture);
|
||||
Back_texture = 0;
|
||||
}
|
||||
|
||||
if (Back_depth_texture) {
|
||||
glDeleteTextures(1, &Back_depth_texture);
|
||||
Back_depth_texture = 0;
|
||||
}
|
||||
|
||||
if (Back_framebuffer) {
|
||||
glDeleteFramebuffers(1, &Back_framebuffer);
|
||||
Back_framebuffer = 0;
|
||||
}
|
||||
|
||||
if (GammaBlit_texture) {
|
||||
glDeleteTextures(1, &GammaBlit_texture);
|
||||
GammaBlit_texture = 0;
|
||||
}
|
||||
|
||||
if (GammaBlit_framebuffer) {
|
||||
glDeleteFramebuffers(1, &GammaBlit_framebuffer);
|
||||
GammaBlit_framebuffer = 0;
|
||||
}
|
||||
|
||||
glDeleteTextures(2, Distortion_texture);
|
||||
Distortion_texture[0] = 0;
|
||||
Distortion_texture[1] = 0;
|
||||
|
||||
if ( Distortion_framebuffer ) {
|
||||
glDeleteFramebuffers(1, &Distortion_framebuffer);
|
||||
Distortion_framebuffer = 0;
|
||||
}
|
||||
opengl_delete_render_texture(Distortion_texture[0]);
|
||||
opengl_delete_render_texture(Distortion_texture[1]);
|
||||
opengl_delete_render_framebuffer(Distortion_framebuffer);
|
||||
|
||||
Scene_texture_initialized = 0;
|
||||
Scene_framebuffer_in_frame = false;
|
||||
}
|
||||
|
||||
void gr_opengl_resize_render_targets()
|
||||
{
|
||||
// Nothing allocated yet (still inside gr_init()), or FBOs are unavailable entirely.
|
||||
if ( !Scene_texture_initialized ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Grow only. Shrinking back would mean reallocating every G-buffer again the moment the window
|
||||
// grew back, and the shrunk state is already handled correctly: Scene_texture_u_scale and
|
||||
// _v_scale confine rendering to the sub-rectangle actually in use. The hardware limit is
|
||||
// applied here rather than left to opengl_setup_scene_textures(), so that a viewport larger
|
||||
// than anything the GPU can allocate compares equal below and stops asking.
|
||||
const int new_width = MIN(MAX(gr_screen.max_w, Scene_texture_width), GL_max_renderbuffer_size);
|
||||
const int new_height = MIN(MAX(gr_screen.max_h, Scene_texture_height), GL_max_renderbuffer_size);
|
||||
|
||||
// The overwhelmingly common case: qtFred calls gr_screen_resize() every frame and the game
|
||||
// calls it on every SDL resize event, almost always at a size the current targets already
|
||||
// cover -- or, past the hardware limit, at one they never will.
|
||||
if ( new_width == Scene_texture_width && new_height == Scene_texture_height ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Tearing down the framebuffer we are currently rendering into would corrupt the frame rather
|
||||
// than fail cleanly, so refuse rather than trying to recover. Callers resize between frames.
|
||||
// Scene_framebuffer_in_frame covers the post-processing passes too: they only ever run inside
|
||||
// gr_scene_texture_begin()/end(), so it is set for the whole of Post_in_frame as well.
|
||||
if ( Scene_framebuffer_in_frame ) {
|
||||
Assertion(false, "Tried to resize the render targets to %dx%d while a scene was being "
|
||||
"rendered into them! The resize has been skipped; the frame will be stretched.",
|
||||
new_width, new_height);
|
||||
return;
|
||||
}
|
||||
|
||||
mprintf(("Growing render targets from %dx%d to %dx%d to cover the new %dx%d viewport.\n",
|
||||
Scene_texture_width, Scene_texture_height, new_width, new_height,
|
||||
gr_screen.max_w, gr_screen.max_h));
|
||||
|
||||
// Leave the framebuffer cache pointing at a name that cannot be deleted out from under it.
|
||||
GL_state.BindFrameBufferBoth(0, 0);
|
||||
|
||||
// Only the size-dependent resources are touched. The post-processing table, the compiled
|
||||
// shaders and the SMAA lookup textures are all resolution-independent and stay alive, which is
|
||||
// what keeps this cheap enough to run off a window drag. The post-processing targets are
|
||||
// rebuilt after the scene textures because they are sized to match them.
|
||||
opengl_scene_texture_shutdown();
|
||||
opengl_setup_scene_textures(new_width, new_height);
|
||||
|
||||
// Reallocating larger is exactly when running out of video memory is most likely, and
|
||||
// opengl_setup_scene_textures() reports that by leaving the scene uninitialized (having
|
||||
// already turned post-processing and soft particles off). Rebuilding the post-processing
|
||||
// targets on top of scene textures that don't exist would only make it worse, so stop here;
|
||||
// the renderer keeps drawing without the offscreen pipeline.
|
||||
if ( !Scene_texture_initialized ) {
|
||||
mprintf(("Failed to allocate %dx%d render targets! The offscreen rendering pipeline has "
|
||||
"been disabled for the rest of this session.\n", new_width, new_height));
|
||||
return;
|
||||
}
|
||||
|
||||
opengl_post_resize_render_targets();
|
||||
}
|
||||
|
||||
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 ) {
|
||||
@@ -800,6 +903,30 @@ void gr_opengl_scene_texture_begin()
|
||||
Scene_texture_v_scale = 1.0f;
|
||||
}
|
||||
|
||||
// A ratio above 1.0 means the viewport outgrew the allocation after the fact -- the scene
|
||||
// texture can't be resized, so all we can do is render the part that fits and let the blit
|
||||
// stretch it back out, which misaligns everything by a different amount on each axis. The
|
||||
// floor above is meant to make this unreachable; if it does happen (a display hotplug or
|
||||
// resolution change after gr_init(), or an allocation clamped by GL_max_renderbuffer_size on
|
||||
// low-end hardware) say so once rather than every frame.
|
||||
if (Scene_texture_u_scale > 1.0f || Scene_texture_v_scale > 1.0f) {
|
||||
static bool reported_undersized_scene_texture = false;
|
||||
|
||||
if (!reported_undersized_scene_texture) {
|
||||
reported_undersized_scene_texture = true;
|
||||
nprintf(("OpenGL",
|
||||
"Viewport (%dx%d) is larger than the scene texture backing it (%dx%d); "
|
||||
"the post-processed image will be stretched to fit.\n",
|
||||
gr_screen.max_w,
|
||||
gr_screen.max_h,
|
||||
Scene_texture_width,
|
||||
Scene_texture_height));
|
||||
}
|
||||
}
|
||||
|
||||
CLAMP(Scene_texture_u_scale, 0.0f, 1.0f);
|
||||
CLAMP(Scene_texture_v_scale, 0.0f, 1.0f);
|
||||
|
||||
if (!light_deferred_enabled()) {
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
@@ -1246,6 +1373,11 @@ void opengl_draw_full_screen_textured(GLfloat u1, GLfloat v1, GLfloat u2, GLfloa
|
||||
opengl_render_primitives_immediate(PRIM_TYPE_TRIS, &vert_def, 3, glVertices, sizeof(glVertices));
|
||||
}
|
||||
|
||||
void opengl_draw_full_screen_scene_texture()
|
||||
{
|
||||
opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_v_scale);
|
||||
}
|
||||
|
||||
void gr_opengl_render_decals(decal_material* material_info,
|
||||
primitive_type prim_type,
|
||||
vertex_layout* layout,
|
||||
|
||||
@@ -55,8 +55,23 @@ void gr_opengl_render_shield_impact(shield_material* material_info,
|
||||
gr_buffer_handle buffer_handle,
|
||||
int n_verts);
|
||||
|
||||
void opengl_setup_scene_textures();
|
||||
void opengl_setup_scene_textures(int width, int height);
|
||||
void opengl_scene_texture_shutdown();
|
||||
void gr_opengl_resize_render_targets();
|
||||
|
||||
// Release a render target, keeping the GL state cache in sync. See the definitions for why the
|
||||
// cache matters now that these are rebuilt mid-session.
|
||||
void opengl_delete_render_texture(GLuint& tex);
|
||||
void opengl_delete_render_framebuffer(GLuint& fbo);
|
||||
|
||||
/**
|
||||
* @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();
|
||||
@@ -147,6 +162,12 @@ void opengl_draw_textured_quad(GLfloat x1,
|
||||
*/
|
||||
void opengl_draw_full_screen_textured(GLfloat u1, GLfloat v1, GLfloat u2, GLfloat v2);
|
||||
|
||||
// Fullscreen pass over a source that is one of the scene/post-processing textures. Those are only
|
||||
// filled out to Scene_texture_u_scale/v_scale of their allocation, so sampling them over the full
|
||||
// [0,1] range would pull in whatever is beyond the rendered region. Prefer this over passing
|
||||
// literal 1.0f extents whenever the bound texture came from that pipeline.
|
||||
void opengl_draw_full_screen_scene_texture();
|
||||
|
||||
inline GLenum opengl_primitive_type(primitive_type prim_type);
|
||||
|
||||
void gr_opengl_start_decal_pass();
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
#include "es_compatibility.h"
|
||||
#endif
|
||||
|
||||
static void opengl_post_setup_render_targets();
|
||||
static void opengl_post_shutdown_render_targets();
|
||||
|
||||
extern bool PostProcessing_override;
|
||||
extern int opengl_check_framebuffer();
|
||||
// Needed to track where the FXAA shaders are
|
||||
@@ -99,7 +102,7 @@ void opengl_post_pass_tonemap()
|
||||
|
||||
GL_state.Texture.Enable(0, GL_TEXTURE_2D, Scene_color_texture);
|
||||
|
||||
opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_u_scale);
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
}
|
||||
|
||||
void opengl_post_pass_bloom()
|
||||
@@ -134,7 +137,10 @@ void opengl_post_pass_bloom()
|
||||
|
||||
GL_state.Texture.Enable(0, GL_TEXTURE_2D, Scene_color_texture);
|
||||
|
||||
opengl_draw_full_screen_textured(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
// Reads the scene texture directly rather than an already-cropped intermediate, so it is
|
||||
// the scaled variant. The blur/composite passes below read Bloom_textures, which this pass
|
||||
// fills edge to edge, so those stay unscaled.
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
}
|
||||
// ------ end bright pass ------
|
||||
|
||||
@@ -293,7 +299,7 @@ void opengl_post_pass_fxaa()
|
||||
|
||||
GL_state.Texture.Enable(0, GL_TEXTURE_2D, Scene_ldr_texture);
|
||||
|
||||
opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_u_scale);
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
|
||||
// set and configure post shader ..
|
||||
opengl_shader_set_current(gr_opengl_maybe_create_shader(SDR_TYPE_POST_PROCESS_FXAA, 0));
|
||||
@@ -310,7 +316,7 @@ void opengl_post_pass_fxaa()
|
||||
|
||||
GL_state.Texture.Enable(0, GL_TEXTURE_2D, Scene_luminance_texture);
|
||||
|
||||
opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_u_scale);
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
|
||||
opengl_shader_set_current();
|
||||
}
|
||||
@@ -333,7 +339,7 @@ static void smaa_detect_edges()
|
||||
|
||||
GL_state.Texture.Enable(0, GL_TEXTURE_2D, Scene_ldr_texture);
|
||||
|
||||
opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_u_scale);
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
}
|
||||
|
||||
static void smaa_calculate_blending_weights()
|
||||
@@ -358,7 +364,7 @@ static void smaa_calculate_blending_weights()
|
||||
GL_state.Texture.Enable(1, GL_TEXTURE_2D, Smaa_area_tex);
|
||||
GL_state.Texture.Enable(2, GL_TEXTURE_2D, Smaa_search_tex);
|
||||
|
||||
opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_u_scale);
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
}
|
||||
|
||||
static void smaa_neighborhood_blending()
|
||||
@@ -381,7 +387,7 @@ static void smaa_neighborhood_blending()
|
||||
GL_state.Texture.Enable(0, GL_TEXTURE_2D, Scene_ldr_texture);
|
||||
GL_state.Texture.Enable(1, GL_TEXTURE_2D, Smaa_blend_tex);
|
||||
|
||||
opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_u_scale);
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
}
|
||||
|
||||
void smaa_resolve()
|
||||
@@ -491,7 +497,7 @@ void opengl_post_lightshafts()
|
||||
GL_state.Blend(GL_TRUE);
|
||||
GL_state.SetAlphaBlendMode(ALPHA_BLEND_ADDITIVE);
|
||||
|
||||
opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_u_scale);
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
|
||||
GL_state.Blend(GL_FALSE);
|
||||
break;
|
||||
@@ -625,7 +631,7 @@ void gr_opengl_post_process_end()
|
||||
// now render it to the screen ...
|
||||
GL_state.PopFramebufferState();
|
||||
|
||||
opengl_draw_full_screen_textured(0.0f, 0.0f, Scene_texture_u_scale, Scene_texture_u_scale);
|
||||
opengl_draw_full_screen_scene_texture();
|
||||
|
||||
//Shadow Map debug window
|
||||
//#define SHADOW_DEBUG
|
||||
@@ -1020,77 +1026,140 @@ static GLuint load_smaa_texture(GLsizei width, GLsizei height, GLenum format, co
|
||||
return tex;
|
||||
}
|
||||
|
||||
static void setup_smaa_resources()
|
||||
// The SMAA area and search textures are fixed-size lookup tables baked into the binary, so unlike
|
||||
// everything else here they survive a resolution change untouched.
|
||||
static void setup_smaa_lookup_textures()
|
||||
{
|
||||
GL_state.PushFramebufferState();
|
||||
|
||||
Smaa_area_tex = load_smaa_texture(AREATEX_WIDTH, AREATEX_HEIGHT, GL_RG8, areaTexBytes, "SMAA Area Texture");
|
||||
Smaa_search_tex =
|
||||
load_smaa_texture(SEARCHTEX_WIDTH, SEARCHTEX_HEIGHT, GL_R8, searchTexBytes, "SMAA Search Texture");
|
||||
}
|
||||
|
||||
static void setup_smaa_render_targets()
|
||||
{
|
||||
setup_smaa_edges_resources();
|
||||
|
||||
setup_smaa_blending_weight_resources();
|
||||
|
||||
setup_smaa_neighborhood_blending_resources();
|
||||
|
||||
GL_state.PopFramebufferState();
|
||||
}
|
||||
|
||||
// generate and test the framebuffer and textures that we are going to use
|
||||
static bool opengl_post_init_framebuffer()
|
||||
static void shutdown_smaa_render_targets()
|
||||
{
|
||||
bool rval = false;
|
||||
opengl_delete_render_texture(Smaa_edges_tex);
|
||||
opengl_delete_render_framebuffer(Smaa_edge_detection_fb);
|
||||
|
||||
// clamp size, if needed
|
||||
Post_texture_width = gr_screen.max_w;
|
||||
Post_texture_height = gr_screen.max_h;
|
||||
opengl_delete_render_texture(Smaa_blend_tex);
|
||||
opengl_delete_render_framebuffer(Smaa_blending_weight_fb);
|
||||
|
||||
if (Post_texture_width > GL_max_renderbuffer_size) {
|
||||
Post_texture_width = GL_max_renderbuffer_size;
|
||||
}
|
||||
opengl_delete_render_texture(Smaa_output_tex);
|
||||
opengl_delete_render_framebuffer(Smaa_neighborhood_blending_fb);
|
||||
}
|
||||
|
||||
if (Post_texture_height > GL_max_renderbuffer_size) {
|
||||
Post_texture_height = GL_max_renderbuffer_size;
|
||||
}
|
||||
// Allocate every post-processing resource whose size follows the scene textures. Split out from
|
||||
// opengl_post_process_init() so gr_opengl_resize_render_targets() can rebuild just these without
|
||||
// re-parsing post_processing.tbl or recompiling shaders.
|
||||
static void opengl_post_setup_render_targets()
|
||||
{
|
||||
// These consume the scene textures pass by pass, so they have to match them exactly rather
|
||||
// than being sized from gr_screen independently -- see gr_opengl_scene_texture_begin() for
|
||||
// what the two sizes diverging would mean.
|
||||
Post_texture_width = Scene_texture_width;
|
||||
Post_texture_height = Scene_texture_height;
|
||||
|
||||
GL_state.PushFramebufferState();
|
||||
|
||||
opengl_setup_bloom_textures();
|
||||
|
||||
// Always set up SMAA resources so the user can switch to an SMAA preset
|
||||
// at runtime even when starting with a non-SMAA AA mode, such as None.
|
||||
//if (Gr_aa_mode != AntiAliasMode::None) {
|
||||
setup_smaa_resources();
|
||||
//}
|
||||
setup_smaa_render_targets();
|
||||
|
||||
GL_state.PopFramebufferState();
|
||||
|
||||
GL_state.BindFrameBuffer(0);
|
||||
|
||||
rval = true;
|
||||
|
||||
if ( opengl_check_for_errors("post_init_framebuffer()") ) {
|
||||
rval = false;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void opengl_post_process_shutdown_bloom()
|
||||
{
|
||||
if ( Bloom_textures[0] ) {
|
||||
glDeleteTextures(1, &Bloom_textures[0]);
|
||||
Bloom_textures[0] = 0;
|
||||
opengl_delete_render_texture(Bloom_textures[0]);
|
||||
opengl_delete_render_texture(Bloom_textures[1]);
|
||||
opengl_delete_render_framebuffer(Bloom_framebuffer);
|
||||
}
|
||||
|
||||
static void opengl_post_shutdown_render_targets()
|
||||
{
|
||||
opengl_post_process_shutdown_bloom();
|
||||
shutdown_smaa_render_targets();
|
||||
}
|
||||
|
||||
void opengl_post_resize_render_targets()
|
||||
{
|
||||
// Post-processing may have been disabled outright (no FBOs, missing shaders, or turned off in
|
||||
// the table), in which case none of these resources exist and none should start existing now.
|
||||
if ( !Post_initialized ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Bloom_textures[1] ) {
|
||||
glDeleteTextures(1, &Bloom_textures[1]);
|
||||
Bloom_textures[1] = 0;
|
||||
opengl_post_shutdown_render_targets();
|
||||
opengl_post_setup_render_targets();
|
||||
}
|
||||
|
||||
// generate and test the framebuffer and textures that we are going to use
|
||||
static bool opengl_post_init_framebuffer()
|
||||
{
|
||||
setup_smaa_lookup_textures();
|
||||
|
||||
opengl_post_setup_render_targets();
|
||||
|
||||
return !opengl_check_for_errors("post_init_framebuffer()");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if ( Bloom_framebuffer > 0 ) {
|
||||
glDeleteFramebuffers(1, &Bloom_framebuffer);
|
||||
Bloom_framebuffer = 0;
|
||||
// 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()
|
||||
@@ -1141,20 +1210,16 @@ void opengl_post_process_shutdown()
|
||||
return;
|
||||
}
|
||||
|
||||
if (Post_framebuffer_id[0]) {
|
||||
glDeleteFramebuffers(1, &Post_framebuffer_id[0]);
|
||||
Post_framebuffer_id[0] = 0;
|
||||
|
||||
if (Post_framebuffer_id[1]) {
|
||||
glDeleteFramebuffers(1, &Post_framebuffer_id[1]);
|
||||
Post_framebuffer_id[1] = 0;
|
||||
}
|
||||
}
|
||||
opengl_delete_render_framebuffer(Post_framebuffer_id[0]);
|
||||
opengl_delete_render_framebuffer(Post_framebuffer_id[1]);
|
||||
|
||||
graphics::Post_processing_manager->clear();
|
||||
graphics::Post_processing_manager = nullptr;
|
||||
|
||||
opengl_post_process_shutdown_bloom();
|
||||
opengl_post_shutdown_render_targets();
|
||||
|
||||
opengl_delete_render_texture(Smaa_area_tex);
|
||||
opengl_delete_render_texture(Smaa_search_tex);
|
||||
|
||||
Post_in_frame = false;
|
||||
Post_active_shader_index = 0;
|
||||
|
||||
@@ -8,6 +8,18 @@
|
||||
void opengl_post_process_init();
|
||||
void opengl_post_process_shutdown();
|
||||
|
||||
// Rebuild the resolution-dependent subset of the above for the current scene texture size, without
|
||||
// re-parsing post_processing.tbl or recompiling shaders. No-op if post-processing isn't active.
|
||||
void opengl_post_resize_render_targets();
|
||||
|
||||
/**
|
||||
* @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();
|
||||
|
||||
@@ -423,6 +433,9 @@ static void opengl_set_default_uniforms(const opengl_shader_t& sdr) {
|
||||
Current_shader->program->Uniforms.setTextureUniform("PositionBuffer", 2);
|
||||
Current_shader->program->Uniforms.setTextureUniform("SpecBuffer", 3);
|
||||
Current_shader->program->Uniforms.setTextureUniform("shadow_map", 4);
|
||||
// Unit 11 matches the model pass. Unit 7 is the model misc map, so do not use it
|
||||
// -- see the raw-sampler bind in gropengldeferred.cpp.
|
||||
Current_shader->program->Uniforms.setTextureUniform("shadow_map_raw", 11);
|
||||
break;
|
||||
|
||||
case SDR_TYPE_PASSTHROUGH_RENDER:
|
||||
|
||||
@@ -37,6 +37,7 @@ void opengl_texture_state::init(GLuint n_units)
|
||||
|
||||
for (unsigned int unit = 0; unit < num_texture_units; unit++) {
|
||||
units[unit].enabled = GL_FALSE;
|
||||
units[unit].bound_sampler = 0;
|
||||
|
||||
default_values(unit);
|
||||
|
||||
@@ -113,6 +114,18 @@ void opengl_texture_state::Enable(GLuint unit, GLenum tex_target, GLuint tex_id)
|
||||
Enable(tex_id);
|
||||
}
|
||||
|
||||
void opengl_texture_state::BindSampler(GLuint unit, GLuint sampler)
|
||||
{
|
||||
Assertion(unit < num_texture_units, "Invalid texture unit value!");
|
||||
|
||||
if (units[unit].bound_sampler == sampler) {
|
||||
return;
|
||||
}
|
||||
|
||||
glBindSampler(unit, sampler);
|
||||
units[unit].bound_sampler = sampler;
|
||||
}
|
||||
|
||||
void opengl_texture_state::Delete(GLuint tex_id)
|
||||
{
|
||||
if (tex_id == 0) {
|
||||
@@ -360,6 +373,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);
|
||||
|
||||
@@ -27,6 +27,12 @@ struct opengl_texture_unit {
|
||||
|
||||
GLenum texture_target;
|
||||
GLuint texture_id;
|
||||
|
||||
// Sampler object bound to this unit, overriding the texture object's own filter/compare
|
||||
// state for as long as it stays bound (0 = none, texture-level state applies as usual).
|
||||
// Tracked here for the same reason texture_id is: so BindSampler() can skip a redundant
|
||||
// glBindSampler call, exactly like Enable() already does for glBindTexture.
|
||||
GLuint bound_sampler;
|
||||
};
|
||||
|
||||
class opengl_texture_state
|
||||
@@ -65,7 +71,20 @@ class opengl_texture_state
|
||||
*/
|
||||
void Enable(GLuint unit, GLenum tex_target, GLuint tex_id);
|
||||
void Delete(GLuint tex_id);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Binds a sampler object to the specified unit, or unbinds one if sampler is 0
|
||||
*
|
||||
* Sampler objects override the bound texture's own filter/compare-mode state for
|
||||
* that unit only, which is otherwise impossible to vary per-unit in this codebase
|
||||
* (texture parameters like GL_TEXTURE_COMPARE_MODE live on the texture object, not
|
||||
* a sampler). No-ops if the unit already has this sampler bound, mirroring Enable().
|
||||
*
|
||||
* @param unit The texture unit to bind the sampler to
|
||||
* @param sampler The sampler object to bind, or 0 to unbind
|
||||
*/
|
||||
void BindSampler(GLuint unit, GLuint sampler);
|
||||
|
||||
inline GLenum GetTarget();
|
||||
inline void SetShaderMode(GLboolean mode);
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
@@ -89,28 +92,6 @@ static auto TextureFilteringOption __UNUSED = options::OptionBuilder<int>("Graph
|
||||
.parser(parse_texture_filtering_func)
|
||||
.finish();
|
||||
|
||||
static SCP_vector<float> anisotropic_value_enumerator()
|
||||
{
|
||||
float max;
|
||||
if (!gr_get_property(gr_property::MAX_ANISOTROPY, &max)) {
|
||||
return SCP_vector<float>();
|
||||
}
|
||||
|
||||
if (max <= 2.0f) {
|
||||
return SCP_vector<float>();
|
||||
}
|
||||
|
||||
SCP_vector<float> out;
|
||||
|
||||
// We assume here that the anisotropy levels are powers of two...
|
||||
float current = 1.0f;
|
||||
while (current <= max) {
|
||||
out.push_back(current);
|
||||
current *= 2.0f;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
static SCP_string anisotropic_display(float val)
|
||||
{
|
||||
if (val < 2.0f) {
|
||||
@@ -134,7 +115,7 @@ static float anisotropic_default()
|
||||
static auto AnisotropyOption = options::OptionBuilder<float>("Graphics.Anisotropy",
|
||||
std::pair<const char*, int>{"Anistropic filtering", 1736},
|
||||
std::pair<const char*, int>{"Controls the amount of anistropic filtering of the textures", 1737})
|
||||
.enumerator(anisotropic_value_enumerator)
|
||||
.enumerator(gr_get_supported_anisotropy_levels)
|
||||
.category(std::make_pair("Graphics", 1825))
|
||||
.display(anisotropic_display)
|
||||
.default_func(anisotropic_default)
|
||||
@@ -146,6 +127,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()
|
||||
@@ -189,8 +191,15 @@ void opengl_tcache_init()
|
||||
// check what mipmap filter we should be using
|
||||
// 0 == Bilinear
|
||||
// 1 == Trilinear
|
||||
// Seed from the legacy config key first: TextureFilteringOption's default_func returns
|
||||
// GL_mipmap_filter, so this read is what supplies that default. Only then let the option
|
||||
// override it, the same order the anisotropy setting below uses.
|
||||
GL_mipmap_filter = os_config_read_uint(NULL, "TextureFilter", 1);
|
||||
|
||||
if (Using_in_game_options) {
|
||||
GL_mipmap_filter = TextureFilteringOption->getValue();
|
||||
}
|
||||
|
||||
if (GL_mipmap_filter > 1) {
|
||||
GL_mipmap_filter = 1;
|
||||
}
|
||||
@@ -236,6 +245,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 +351,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 +371,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 +750,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 +987,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 +1058,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 +1135,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 +1672,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 +1685,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 +1806,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 +1844,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 +2012,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 +2040,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 +2070,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 +2078,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,15 +59,35 @@ 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;
|
||||
|
||||
GLuint Shadow_map_depth_texture = 0;
|
||||
GLuint Shadow_map_raw_sampler = 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 +295,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 +387,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 +514,25 @@ 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;
|
||||
|
||||
// Second sampler for Shadow_map_depth_texture, compare mode off, so PCSS blocker
|
||||
// search (shadows.sdr) can read raw depth off the same texture that the compare
|
||||
// sampler reads filtered visibility off -- see shadow_contact_hardening_supported()
|
||||
// for why this needs a sampler object rather than a second glTexParameteri call.
|
||||
if (shadow_contact_hardening_supported()) {
|
||||
glGenSamplers(1, &Shadow_map_raw_sampler);
|
||||
// GL_NEAREST: the blocker search wants the actual stored per-texel depth, not a
|
||||
// hardware-filtered blend across the compare boundary -- deliberately different
|
||||
// from the compare sampler's GL_LINEAR.
|
||||
glSamplerParameteri(Shadow_map_raw_sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glSamplerParameteri(Shadow_map_raw_sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glSamplerParameteri(Shadow_map_raw_sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glSamplerParameteri(Shadow_map_raw_sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glSamplerParameteri(Shadow_map_raw_sampler, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
||||
glSamplerParameteri(Shadow_map_raw_sampler, GL_TEXTURE_COMPARE_MODE, GL_NONE);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -517,29 +563,28 @@ 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();
|
||||
|
||||
if (Shadow_quality != ShadowQuality::Disabled) {
|
||||
int size;
|
||||
switch (Shadow_quality) {
|
||||
case ShadowQuality::Low:
|
||||
size = 512;
|
||||
break;
|
||||
case ShadowQuality::Medium:
|
||||
size = 1024;
|
||||
break;
|
||||
case ShadowQuality::High:
|
||||
size = 2048;
|
||||
break;
|
||||
case ShadowQuality::Ultra:
|
||||
size = 4096;
|
||||
break;
|
||||
default:
|
||||
size = 256;
|
||||
break;
|
||||
}
|
||||
const int size = shadows_map_resolution();
|
||||
|
||||
if (!opengl_init_shadow_framebuffer(size)) {
|
||||
mprintf(("Failed to create either shadow framebuffer. Disabling shadow support.\n"));
|
||||
@@ -565,6 +610,16 @@ void opengl_tnl_shutdown()
|
||||
|
||||
shadow_cascade_params_shutdown();
|
||||
|
||||
if ( Shadow_map_raw_sampler ) {
|
||||
// Clear the tracked bindings before deleting -- glDeleteSamplers silently unbinds
|
||||
// the sampler from every unit on the GL side, which would leave GL_state's tracking
|
||||
// stale (BindSampler() would then skip a rebind of a since-deleted sampler name).
|
||||
GL_state.Texture.BindSampler(7, 0);
|
||||
GL_state.Texture.BindSampler(11, 0);
|
||||
glDeleteSamplers(1, &Shadow_map_raw_sampler);
|
||||
Shadow_map_raw_sampler = 0;
|
||||
}
|
||||
|
||||
if ( Shadow_map_depth_texture ) {
|
||||
glDeleteTextures(1, &Shadow_map_depth_texture);
|
||||
Shadow_map_depth_texture = 0;
|
||||
@@ -732,7 +787,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 +814,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();
|
||||
@@ -905,8 +972,14 @@ void opengl_tnl_set_model_material(model_material *material_info)
|
||||
Current_shader->program->Uniforms.setTextureUniform("sAmbientmap", 6);
|
||||
if (setAllUniforms || (flags & MODEL_SDR_FLAG_MISC))
|
||||
Current_shader->program->Uniforms.setTextureUniform("sMiscmap", 7);
|
||||
if (setAllUniforms || (flags & MODEL_SDR_FLAG_SHADOWS))
|
||||
if (setAllUniforms || (flags & MODEL_SDR_FLAG_SHADOWS)) {
|
||||
Current_shader->program->Uniforms.setTextureUniform("shadow_map", 8);
|
||||
// Unit 11 is reserved for this -- see the raw-sampler bind alongside "shadow_map"
|
||||
// below. Set unconditionally (even when contact hardening is unsupported) for the
|
||||
// same AMD unbound-uniform reason as the rest of this block; the shader's own
|
||||
// penumbra_scale sentinel check is what prevents the read at runtime.
|
||||
Current_shader->program->Uniforms.setTextureUniform("shadow_map_raw", 11);
|
||||
}
|
||||
Current_shader->program->Uniforms.setTextureUniform("sFramebuffer", 9);
|
||||
if (setAllUniforms || (flags & MODEL_SDR_FLAG_TRANSFORM))
|
||||
Current_shader->program->Uniforms.setTextureUniform("transform_tex", 10);
|
||||
@@ -989,6 +1062,10 @@ void opengl_tnl_set_model_material(model_material *material_info)
|
||||
|
||||
if (material_info->is_shadow_receiving()) {
|
||||
GL_state.Texture.Enable(8, GL_TEXTURE_2D_ARRAY, Shadow_map_depth_texture);
|
||||
if (Shadow_map_raw_sampler) {
|
||||
GL_state.Texture.Enable(11, GL_TEXTURE_2D_ARRAY, Shadow_map_depth_texture);
|
||||
GL_state.Texture.BindSampler(11, Shadow_map_raw_sampler);
|
||||
}
|
||||
}
|
||||
|
||||
if (material_info->get_animated_effect() >= 0) {
|
||||
@@ -1136,6 +1213,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;
|
||||
|
||||
|
||||
@@ -35,6 +35,11 @@ extern float shadow_fardist;
|
||||
|
||||
extern GLuint Shadow_map_depth_texture;
|
||||
|
||||
// Second sampler object bound to Shadow_map_depth_texture, compare mode off, for the raw
|
||||
// (uncompared) depth reads PCSS blocker search needs. Only created when
|
||||
// shadow_contact_hardening_supported() is true; 0 otherwise. See gropengltnl.cpp.
|
||||
extern GLuint Shadow_map_raw_sampler;
|
||||
|
||||
struct opengl_vertex_bind {
|
||||
vertex_format_data::vertex_format format;
|
||||
GLint size;
|
||||
@@ -53,6 +58,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.
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#include "graphics/rtao.h"
|
||||
|
||||
#include "graphics/2d.h"
|
||||
#include "graphics/shadows.h"
|
||||
#include "options/Option.h"
|
||||
|
||||
int Rtao_samples = 0;
|
||||
|
||||
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
|
||||
auto RtaoQualityOption = options::OptionBuilder<int>("Graphics.RtaoQuality",
|
||||
std::pair<const char*, int>{"Raytraced Ambient Occlusion", -1},
|
||||
std::pair<const char*, int>{"Rays traced per pixel for ambient occlusion in the deferred ambient pass; Off keeps baked AO maps only", -1})
|
||||
.enumerator([]() -> SCP_vector<int> {
|
||||
if (rtao_supported()) {
|
||||
return {0, 4, 8, 16};
|
||||
}
|
||||
return {0}; // inert default when RT isn't supported
|
||||
})
|
||||
.display(options::MapValueDisplay<int>({
|
||||
{0, {"Off", -1}},
|
||||
{4, {"Low", -1}},
|
||||
{8, {"Medium", -1}},
|
||||
{16, {"High", -1}}
|
||||
}))
|
||||
.bind_to(&Rtao_samples)
|
||||
.flags({options::OptionFlags::ForceMultiValueSelection})
|
||||
.level(options::ExpertLevel::Advanced)
|
||||
.category(std::make_pair("Graphics", 1825))
|
||||
.default_func([]() { return 0; })
|
||||
.importance(71)
|
||||
.finish();
|
||||
|
||||
bool rtao_supported()
|
||||
{
|
||||
return gr_is_capable(gr_capability::CAPABILITY_RAYTRACED_SHADOWS);
|
||||
}
|
||||
|
||||
bool rtao_enabled()
|
||||
{
|
||||
// The shadow cascade parameters that RTAO reads are only created when shadows are on.
|
||||
return Rtao_samples > 0 && rtao_supported() && Shadow_quality != ShadowQuality::Disabled;
|
||||
}
|
||||
|
||||
void rtao_remove_unsupported_options()
|
||||
{
|
||||
if (!rtao_supported()) {
|
||||
// The option has no effect without raytracing support, so remove it.
|
||||
options::OptionsManager::instance()->removeOption(RtaoQualityOption);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
// Raytraced ambient occlusion (RTAO): short hemisphere occlusion rays traced via
|
||||
// inline ray query against the raytraced-shadow TLAS, inside the deferred ambient
|
||||
// light pass (LT_AMBIENT branch of deferred-f.sdr, RTAO shader variant). Composes
|
||||
// multiplicatively with baked AO maps through the shared G-buffer `ao` term, so
|
||||
// env-map/IBL ambient darkens consistently for free. The AO radius and strength
|
||||
// are content-scale-dependent and therefore mod-owned -- see $RTAO Radius: /
|
||||
// $RTAO Strength: in lighting_profiles.h.
|
||||
|
||||
// Rays traced per pixel for ambient occlusion. 0 (the default) disables RTAO
|
||||
// entirely; nonzero values only take effect when rtao_supported(). Clamped
|
||||
// in-shader to RT_SHADOW_MAX_SAMPLES (16). See RtaoQualityOption in rtao.cpp.
|
||||
extern int Rtao_samples;
|
||||
|
||||
// Whether the hardware/renderer can trace AO rays at all -- the same requirement
|
||||
// set as raytraced shadows (Vulkan + VK_KHR_acceleration_structure +
|
||||
// VK_KHR_ray_query), so this simply forwards to that capability.
|
||||
bool rtao_supported();
|
||||
|
||||
// Whether the deferred ambient pass should trace AO this frame, i.e. the user has
|
||||
// enabled it, the hardware supports it, AND shadows are on. This is the single source of truth --
|
||||
// gate any RTAO shader-flag, TLAS-build, or uniform code on this, not on
|
||||
// Rtao_samples/rtao_supported() separately.
|
||||
bool rtao_enabled();
|
||||
|
||||
// Remove the RTAO option from the options list when the renderer cannot trace rays.
|
||||
void rtao_remove_unsupported_options();
|
||||
@@ -133,6 +133,8 @@ static ShaderVariantInfo SHADER_VARIANTS[] = {
|
||||
|
||||
{SDR_TYPE_DEFERRED_LIGHTING, false, SDR_FLAG_DEFERRED_RT_SHADOWS, "RT_SHADOWS", {}, "Use raytraced (TLAS ray query) shadows instead of cascaded shadow maps"},
|
||||
|
||||
{SDR_TYPE_DEFERRED_LIGHTING, false, SDR_FLAG_DEFERRED_RTAO, "RTAO", {}, "Trace raytraced ambient occlusion in the ambient light pass"},
|
||||
|
||||
{SDR_TYPE_POST_PROCESS_BLUR, false, SDR_FLAG_BLUR_HORIZONTAL, "PASS_0", {}, "Horizontal blur pass"},
|
||||
|
||||
{SDR_TYPE_POST_PROCESS_BLUR, false, SDR_FLAG_BLUR_VERTICAL, "PASS_1", {}, "Vertical blur pass"},
|
||||
@@ -162,6 +164,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) {
|
||||
@@ -208,7 +223,7 @@ bool shader_variant_requires_raytracing(shader_type type, unsigned int flags)
|
||||
case SDR_TYPE_MODEL:
|
||||
return (flags & MODEL_SDR_FLAG_RT_SHADOWS) != 0;
|
||||
case SDR_TYPE_DEFERRED_LIGHTING:
|
||||
return (flags & SDR_FLAG_DEFERRED_RT_SHADOWS) != 0;
|
||||
return (flags & (SDR_FLAG_DEFERRED_RT_SHADOWS | SDR_FLAG_DEFERRED_RTAO)) != 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
+211
-15
@@ -16,7 +16,9 @@
|
||||
#include "cmdline/cmdline.h"
|
||||
#include "debris/debris.h"
|
||||
#include "graphics/matrix.h"
|
||||
#include "graphics/rtao.h"
|
||||
#include "lighting/lighting.h"
|
||||
#include "lighting/lighting_profiles.h"
|
||||
#include "math/vecmat.h"
|
||||
#include "mod_table/mod_table.h"
|
||||
#include "model/model.h"
|
||||
@@ -27,9 +29,13 @@
|
||||
#include "ship/ship.h"
|
||||
#include "ship/shipfx.h"
|
||||
#include "render/3d.h"
|
||||
#include "starfield/starfield.h"
|
||||
#include "starfield/sun_disc.h"
|
||||
#include "tracing/tracing.h"
|
||||
#include "util/uniform_structs.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
extern vec3d check_offsets[8];
|
||||
|
||||
matrix4 Shadow_view_matrix_light;
|
||||
@@ -45,6 +51,7 @@ vec3d shadow_rt_relative(const vec3d& world_pos)
|
||||
}
|
||||
SCP_vector<matrix4> Shadow_proj_matrix;
|
||||
SCP_vector<float> Shadow_cascade_distances;
|
||||
SCP_vector<float> Shadow_penumbra_scale;
|
||||
|
||||
static SCP_vector<light_frustum_info> Shadow_frustums;
|
||||
|
||||
@@ -243,13 +250,165 @@ auto RtShadowBiasMaxOption = options::OptionBuilder<float>("Graphics.RtShadowBia
|
||||
.importance(74)
|
||||
.finish();
|
||||
|
||||
int Rt_shadow_samples_override = -1;
|
||||
|
||||
bool Shadow_contact_hardening_enabled = true;
|
||||
|
||||
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
|
||||
auto ShadowContactHardeningOption = options::OptionBuilder<bool>("Graphics.ShadowContactHardening",
|
||||
std::pair<const char*, int>{"Shadow Contact Hardening", -1},
|
||||
std::pair<const char*, int>{"Sizes each shadow's penumbra by how close its occluder is, instead of a fixed blur width. Costs an extra shadow-map search per shadowed pixel", -1})
|
||||
// Only ever offered on hardware that can do the raw-depth read the
|
||||
// blocker search needs -- see shadow_contact_hardening_supported().
|
||||
.enumerator([]() -> SCP_vector<bool> {
|
||||
if (shadow_contact_hardening_supported()) {
|
||||
return {true, false};
|
||||
}
|
||||
return {false}; // inert default when unsupported
|
||||
})
|
||||
// Live-apply: shadows_start_render() reads Shadow_contact_hardening_enabled()
|
||||
// fresh every frame, no restart needed.
|
||||
.bind_to(&Shadow_contact_hardening_enabled)
|
||||
.flags({options::OptionFlags::ForceMultiValueSelection})
|
||||
.level(options::ExpertLevel::Advanced)
|
||||
.category(std::make_pair("Graphics", 1825))
|
||||
.default_func([]() { return true; })
|
||||
.importance(73)
|
||||
.finish();
|
||||
|
||||
bool shadows_raytracing_supported()
|
||||
{
|
||||
return gr_is_capable(gr_capability::CAPABILITY_RAYTRACED_SHADOWS);
|
||||
}
|
||||
|
||||
bool shadow_contact_hardening_supported()
|
||||
{
|
||||
return gr_is_capable(gr_capability::CAPABILITY_SHADOW_CONTACT_HARDENING);
|
||||
}
|
||||
|
||||
bool shadow_contact_hardening_enabled()
|
||||
{
|
||||
return shadow_contact_hardening_supported() && Shadow_contact_hardening_enabled;
|
||||
}
|
||||
|
||||
int shadows_map_resolution()
|
||||
{
|
||||
switch (Shadow_quality) {
|
||||
case ShadowQuality::Low: return 512;
|
||||
case ShadowQuality::Medium: return 1024;
|
||||
case ShadowQuality::High: return 2048;
|
||||
case ShadowQuality::Ultra: return 4096;
|
||||
default: return 512;
|
||||
}
|
||||
}
|
||||
|
||||
int shadows_rt_sample_count()
|
||||
{
|
||||
if (Rt_shadow_samples_override > 0) {
|
||||
return Rt_shadow_samples_override;
|
||||
}
|
||||
|
||||
// The same tiers that pick the shadow map's resolution pick the ray count, so one
|
||||
// setting covers "how much am I willing to spend on shadows" for either method. Low
|
||||
// stays at a single ray -- that is the hard-shadow, one-ray-per-light floor, matching
|
||||
// how a 512 map is the cheap floor for the other method rather than a soft-shadow
|
||||
// setting the user has to find separately.
|
||||
switch (Shadow_quality) {
|
||||
case ShadowQuality::Low: return 1;
|
||||
case ShadowQuality::Medium: return 4;
|
||||
case ShadowQuality::High: return 8;
|
||||
case ShadowQuality::Ultra: return 16;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// The apparent sun size the $Shadow Smoothness Factor: values are taken to be tuned against.
|
||||
// Sol is the natural choice: measured across the sun art of retail, the MediaVPs and several
|
||||
// mods, the calibration in sun_disc.cpp lands median content near Sol, and the shipped
|
||||
// smoothness defaults (1/300 .. 1/200) work out to a Sol-sized sun's penumbra at a blocker
|
||||
// distance of roughly 0.7-1.1 cascade widths. So a Sol-sized sun reproduces the look every
|
||||
// existing mod already tuned for, and everything else scales from there.
|
||||
static float shadow_smoothness_reference_tangent()
|
||||
{
|
||||
return sun_disc_tangent_from_diameter(SUN_ANGULAR_SIZE_SOL);
|
||||
}
|
||||
|
||||
// Softness of the shadow-mapped sun relative to that reference, i.e. the factor the tabled
|
||||
// smoothness values get scaled by.
|
||||
//
|
||||
// This is what makes shadow softness method-agnostic: the shadow map and the raytracer are
|
||||
// driven by the same number -- the tangent of the sun's angular radius, from $SunAngularSize:
|
||||
// or measured off the sun bitmap (see sun_angular_radius_tangent() in starfield.cpp). Doubling
|
||||
// a sun's apparent size doubles the raytraced penumbra cone and this filter width alike, so a
|
||||
// mission author tunes one parameter and both methods follow.
|
||||
//
|
||||
// Where shadow_contact_hardening_supported() is true, the value returned here no longer *is*
|
||||
// the penumbra radius -- it's the ceiling pcssPenumbraRadius() (shadows.sdr) clamps a real,
|
||||
// per-pixel, blocker-search-driven radius to (see Shadow_penumbra_scale). Where it's false
|
||||
// (GL below 3.3), it's still used directly as the fixed radius, exactly
|
||||
// as before contact hardening existed -- this function's output means "the widest the penumbra
|
||||
// is allowed to get" either way, which is why the tabled values keep their old meaning for mods
|
||||
// that already tuned them.
|
||||
//
|
||||
// Uses Static_light.front(), the same light shadows_start_render() builds the cascades from.
|
||||
// Every directional light that reaches this point carries a source radius: suns get theirs in
|
||||
// stars_draw_sun(), and common_setup_room_lights() -- the only other producer -- gives its
|
||||
// stand-in lights a sun's. A zero radius therefore means a genuinely sizeless source (a mod's
|
||||
// deliberately blank sun bitmap, say), which asks for a hard edge; the shadow map answers with
|
||||
// the narrowest filter it can, see shadow_clamp_smoothness().
|
||||
static float shadow_smoothness_scale()
|
||||
{
|
||||
if (Static_light.empty()) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
const float reference = shadow_smoothness_reference_tangent();
|
||||
if (reference <= 0.0f) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
// For directional lights source_radius is the tangent of the angular radius, not a
|
||||
// world-space size -- see light_add_directional() in starfield.cpp's stars_draw_sun().
|
||||
return Static_light.front().source_radius / reference;
|
||||
}
|
||||
|
||||
// Keeps a derived filter width inside what the shadow map can actually represent.
|
||||
//
|
||||
// The floor is one texel: a sun small enough to want a sub-texel penumbra can't get one out of
|
||||
// a shadow map, and dropping below a texel only collapses all 16 Poisson taps into the same
|
||||
// texel -- paying for the taps and getting an aliased edge for it. (A raytraced shadow *does*
|
||||
// go properly hard there; this is a limit of the method, not of the parameter.)
|
||||
//
|
||||
// The ceiling bounds how far the taps spread. Missions set +AngularSize: freely, and a large
|
||||
// enough sun would scatter the 16 taps far enough apart to read as separate blobs, while the
|
||||
// comparison depth stays at the receiver and turns the widened filter into acne.
|
||||
//
|
||||
// Note the floor also applies to the tabled values themselves, so a mod that asked for a very
|
||||
// small $Shadow Smoothness Factor: gets widened to a texel at low shadow-map resolutions. That
|
||||
// value was already sub-texel there -- i.e. already doing nothing except cost taps -- so this
|
||||
// trades a hair of extra blur for an antialiased edge.
|
||||
static float shadow_clamp_smoothness(float smoothness)
|
||||
{
|
||||
// shadows_map_resolution() is 512 at its smallest, so the floor always stays below the
|
||||
// ceiling and std::clamp()'s lo <= hi precondition holds.
|
||||
const float min_smoothness = 1.0f / static_cast<float>(shadows_map_resolution());
|
||||
constexpr float max_smoothness = 0.02f;
|
||||
|
||||
return std::clamp(smoothness, min_smoothness, max_smoothness);
|
||||
}
|
||||
|
||||
void shadows_remove_unsupported_options()
|
||||
{
|
||||
rtao_remove_unsupported_options();
|
||||
|
||||
if (!shadow_contact_hardening_supported()) {
|
||||
// No raw-depth sampler support (pre-3.3 OpenGL): the option is inert, so drop it.
|
||||
// The bound global keeps its default; shadow_contact_hardening_enabled() folding
|
||||
// in shadow_contact_hardening_supported() keeps rendering on the fixed-width
|
||||
// fallback either way.
|
||||
options::OptionsManager::instance()->removeOption(ShadowContactHardeningOption);
|
||||
}
|
||||
|
||||
if (shadows_raytracing_supported()) {
|
||||
return;
|
||||
}
|
||||
@@ -670,6 +829,9 @@ matrix shadows_start_render(matrix *eye_orient, vec3d *eye_pos, fov_t fov, fov_t
|
||||
// Only ever do cockpit cascades if there's no override
|
||||
bool render_cockpit_cascades = !cascade_distances_override.has_value() && ship_render_player_has_closeup_visuals();
|
||||
|
||||
// Computed once, not per-cascade -- see shadow_contact_hardening_enabled().
|
||||
const bool contact_hardening_active = shadow_contact_hardening_enabled();
|
||||
|
||||
for (int i = 0; i < num_cascades; i++) {
|
||||
float z_near;
|
||||
if (i == 0 || (!render_cockpit_cascades && i == Num_cockpit_shadow_cascades)) {
|
||||
@@ -682,6 +844,18 @@ matrix shadows_start_render(matrix *eye_orient, vec3d *eye_pos, fov_t fov, fov_t
|
||||
shadows_construct_light_frustum(&Shadow_frustums[i], &light_matrix, eye_orient, nullptr, i < Num_cockpit_shadow_cascades ? cockpit_fov : fov, aspect, z_near, z_far);
|
||||
Shadow_cascade_distances[i] = cascade_distances_actual[i];
|
||||
Shadow_proj_matrix[i] = Shadow_frustums[i].proj_matrix;
|
||||
|
||||
// PCSS penumbra scale: world-space blocker/receiver depth separation (read back from
|
||||
// the shadow map by the blocker search) times this equals the UV-space penumbra
|
||||
// radius directly -- see the derivation next to shadow_smoothness_scale() below.
|
||||
// Sentinel -1 when unsupported OR the user has the option off tells the shader to
|
||||
// fall back to the fixed smoothness_factors[cascade] (today's behavior) instead of
|
||||
// reading a raw depth sampler -- which either doesn't exist on this hardware, or
|
||||
// does but isn't worth the extra shadow-map search right now.
|
||||
Shadow_penumbra_scale[i] = contact_hardening_active
|
||||
? std::max(0.0f, (Shadow_frustums[i].max.xyz.z - Shadow_frustums[i].min.xyz.z) * lp.source_radius /
|
||||
(Shadow_frustums[i].max.xyz.x - Shadow_frustums[i].min.xyz.x))
|
||||
: -1.0f;
|
||||
}
|
||||
|
||||
gr_shadow_map_start(&Shadow_view_matrix_light, &light_matrix, eye_pos, true);
|
||||
@@ -1020,6 +1194,10 @@ void shadow_end_frame() {
|
||||
static gr_buffer_handle Shadow_cascade_params_buffer;
|
||||
static size_t Shadow_cascade_params_buffer_size = 0;
|
||||
|
||||
// Number of padded per-cascade float arrays shadow_cascade_params_bind() writes after the
|
||||
// proj matrices: cascade distances, smoothness factors, PCSS penumbra scale.
|
||||
static constexpr size_t Num_cascade_float_arrays = 3;
|
||||
|
||||
static std::pair<size_t, size_t> compute_cascade_params_size(int num_cascades) {
|
||||
size_t padding_required = num_cascades % 4;
|
||||
if (padding_required != 0)
|
||||
@@ -1027,11 +1205,23 @@ static std::pair<size_t, size_t> compute_cascade_params_size(int num_cascades) {
|
||||
|
||||
return {sizeof(graphics::shadow_cascade_static_data)
|
||||
+ sizeof(matrix4) * num_cascades
|
||||
+ sizeof(float) * (num_cascades + padding_required)
|
||||
+ sizeof(float) * (num_cascades + padding_required),
|
||||
+ Num_cascade_float_arrays * sizeof(float) * (num_cascades + padding_required),
|
||||
padding_required};
|
||||
}
|
||||
|
||||
// Writes num_cascades floats (value_at(0)..value_at(num_cascades - 1)) into buffer at offset,
|
||||
// advancing offset past both the values and the std140 padding that keeps the next field
|
||||
// vec4-aligned. Shared by shadow_cascade_params_bind()'s three per-cascade float arrays so the
|
||||
// offset/padding bookkeeping can't drift between them.
|
||||
template <typename F>
|
||||
static void write_padded_floats(SCP_vector<uint8_t>& buffer, size_t& offset, int num_cascades, size_t padding, F&& value_at) {
|
||||
for (int i = 0; i < num_cascades; i++) {
|
||||
*reinterpret_cast<float*>(buffer.data() + offset) = value_at(i);
|
||||
offset += sizeof(float);
|
||||
}
|
||||
offset += sizeof(float) * padding;
|
||||
}
|
||||
|
||||
void shadow_cascade_params_init() {
|
||||
const int num_cascades = Num_shadow_cascades + Num_cockpit_shadow_cascades;
|
||||
Shadow_cascade_params_buffer_size = compute_cascade_params_size(num_cascades).first;
|
||||
@@ -1043,6 +1233,7 @@ void shadow_cascade_params_init() {
|
||||
Shadow_frustums.resize(num_cascades);
|
||||
Shadow_cascade_distances.resize(num_cascades);
|
||||
Shadow_proj_matrix.resize(num_cascades);
|
||||
Shadow_penumbra_scale.resize(num_cascades);
|
||||
}
|
||||
|
||||
void shadow_cascade_params_shutdown() {
|
||||
@@ -1062,7 +1253,7 @@ void shadow_cascade_params_bind(int cascade_offset, int cascade_count, const sha
|
||||
const auto [required_size, padding] = compute_cascade_params_size(num_cascades);
|
||||
|
||||
Assertion(required_size <= Shadow_cascade_params_buffer_size, "The shadow cascade parameter buffer grew in size!");
|
||||
Assertion(Shadow_proj_matrix.size() == static_cast<size_t>(num_cascades) && Shadow_cascade_distances.size() == static_cast<size_t>(num_cascades) && Shadow_smoothness_factor.size() == static_cast<size_t>(num_cascades), "Shadow cascade data buffers are of incorrect size! (Expected %d, got %d, %d, %d)", num_cascades, static_cast<int>(Shadow_proj_matrix.size()), static_cast<int>(Shadow_cascade_distances.size()), static_cast<int>(Shadow_smoothness_factor.size()));
|
||||
Assertion(Shadow_proj_matrix.size() == static_cast<size_t>(num_cascades) && Shadow_cascade_distances.size() == static_cast<size_t>(num_cascades) && Shadow_smoothness_factor.size() == static_cast<size_t>(num_cascades) && Shadow_penumbra_scale.size() == static_cast<size_t>(num_cascades), "Shadow cascade data buffers are of incorrect size! (Expected %d, got %d, %d, %d, %d)", num_cascades, static_cast<int>(Shadow_proj_matrix.size()), static_cast<int>(Shadow_cascade_distances.size()), static_cast<int>(Shadow_smoothness_factor.size()), static_cast<int>(Shadow_penumbra_scale.size()));
|
||||
Assertion(cascade_count + cascade_offset <= num_cascades, "Requested drawing out-of-range cascades!");
|
||||
|
||||
SCP_vector<uint8_t> buffer(required_size, 0);
|
||||
@@ -1074,6 +1265,10 @@ void shadow_cascade_params_bind(int cascade_offset, int cascade_count, const sha
|
||||
static_data.cascade_count = cascade_count;
|
||||
static_data.rtShadowBiasMin = Rt_shadow_bias_min;
|
||||
static_data.rtShadowBiasMax = Rt_shadow_bias_max;
|
||||
static_data.rtShadowSampleCount = shadows_rt_sample_count();
|
||||
static_data.rtaoSampleCount = Rtao_samples;
|
||||
static_data.rtaoRadius = lighting_profiles::current_rtao_radius();
|
||||
static_data.rtaoStrength = lighting_profiles::current_rtao_strength();
|
||||
static_data.shadow_mv_matrix = Shadow_view_matrix_light;
|
||||
|
||||
static_data.shadow_ray_cull_mask = ray.cull_mask;
|
||||
@@ -1089,19 +1284,20 @@ void shadow_cascade_params_bind(int cascade_offset, int cascade_count, const sha
|
||||
offset += sizeof(matrix4);
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_cascades; i++) {
|
||||
auto& cascade_distance = *reinterpret_cast<float*>(buffer.data() + offset);
|
||||
cascade_distance = Shadow_cascade_distances[i];
|
||||
offset += sizeof(float);
|
||||
}
|
||||
offset += sizeof(float) * padding;
|
||||
write_padded_floats(buffer, offset, num_cascades, padding,
|
||||
[](int i) { return Shadow_cascade_distances[i]; });
|
||||
|
||||
for (int i = 0; i < num_cascades; i++) {
|
||||
auto& smoothness_factor = *reinterpret_cast<float*>(buffer.data() + offset);
|
||||
smoothness_factor = Shadow_smoothness_factor[i];
|
||||
offset += sizeof(float);
|
||||
}
|
||||
offset += sizeof(float) * padding;
|
||||
// Scaled by the sun's apparent size so shadow-mapped and raytraced shadows soften
|
||||
// together -- see shadow_smoothness_scale().
|
||||
const float smoothness_scale = shadow_smoothness_scale();
|
||||
|
||||
write_padded_floats(buffer, offset, num_cascades, padding,
|
||||
[smoothness_scale](int i) { return shadow_clamp_smoothness(Shadow_smoothness_factor[i] * smoothness_scale); });
|
||||
|
||||
// PCSS penumbra scale, appended last so every field above keeps its existing byte
|
||||
// offset -- see Shadow_penumbra_scale's derivation comment in shadows.h.
|
||||
write_padded_floats(buffer, offset, num_cascades, padding,
|
||||
[](int i) { return Shadow_penumbra_scale[i]; });
|
||||
|
||||
// Full replacement, not _offset: this runs several times per frame with different content,
|
||||
// and Vulkan's streaming buffer only bump-allocates fresh memory for a full update.
|
||||
|
||||
@@ -67,6 +67,32 @@ extern int Max_rt_shadow_local_lights;
|
||||
extern float Rt_shadow_bias_min;
|
||||
extern float Rt_shadow_bias_max;
|
||||
|
||||
// Rays traced per pixel per shadowed light, for lights that have a source size --
|
||||
// suns from their drawn size, or from $SunAngularSize in stars.tbl when that
|
||||
// overrides it; local lights via their source_radius. 1 means the single hard ray
|
||||
// in traceShadowRayCone() (shadows.sdr); above that it samples a penumbra.
|
||||
//
|
||||
// This is a cost knob, not an appearance knob: how *wide* a penumbra is comes
|
||||
// solely from the light's source size, which is what also drives the shadow-map
|
||||
// path and keeps the two methods consistent for content authors. The sample count
|
||||
// only decides how well that penumbra resolves.
|
||||
//
|
||||
// Which is why it is not a setting of its own: it follows Shadow_quality
|
||||
// (Low/Medium/High/Ultra -> 1/4/8/16), the same tier that picks the shadow map's
|
||||
// resolution, so one control covers shadow cost whichever method is active.
|
||||
// Capped in-shader at RT_SHADOW_MAX_SAMPLES (16).
|
||||
int shadows_rt_sample_count();
|
||||
|
||||
// Session-only override for shadows_rt_sample_count(), for iterating in the lab
|
||||
// without touching a persisted setting. <= 0 means "follow Shadow_quality".
|
||||
extern int Rt_shadow_samples_override;
|
||||
|
||||
// Edge length, in texels, of one square cascade of the shadow map, as chosen by
|
||||
// Shadow_quality. Meaningless when Shadow_quality is Disabled (callers guard on that
|
||||
// first); the value returned in that case is only there to keep the result usable as a
|
||||
// divisor.
|
||||
int shadows_map_resolution();
|
||||
|
||||
// Whether the current hardware/renderer can do anything with ShadowRenderMethod::Raytraced
|
||||
// at all (Vulkan + VK_KHR_acceleration_structure + VK_KHR_ray_query support). Independent
|
||||
// of which method is currently selected -- use this to decide whether to offer the choice.
|
||||
@@ -105,6 +131,38 @@ constexpr uint8_t TLAS_MASK_ALL = 0xFF;
|
||||
constexpr uint8_t TLAS_MASK_VIEWER_HULL = 0x80;
|
||||
constexpr uint8_t SHADOW_RAY_CULL_MASK_EXCLUDE_VIEWER_HULL = static_cast<uint8_t>(~TLAS_MASK_VIEWER_HULL);
|
||||
|
||||
// Per-cascade PCSS penumbra scale: world-space blocker/receiver depth separation, scaled
|
||||
// by the sun's tanθ and converted to UV space, gives the contact-hardened penumbra radius
|
||||
// directly (see the derivation comment above shadow_smoothness_scale() in shadows.cpp).
|
||||
// A negative value is a sentinel meaning shadow_contact_hardening_enabled() was false
|
||||
// when it was computed -- the shader falls back to the fixed smoothness_factors[cascade].
|
||||
extern SCP_vector<float> Shadow_penumbra_scale;
|
||||
|
||||
// Whether the current renderer can do a raw (uncompared) depth read off the shadow map,
|
||||
// which PCSS blocker search needs. True on Vulkan always; on OpenGL requires
|
||||
// GL 3.3 (a second sampler object with compare mode off, bound to the
|
||||
// same shadow texture -- see shadow_map_raw in shadows.sdr). Independent of whether the
|
||||
// user currently has contact hardening turned on -- use this to decide whether to offer
|
||||
// the option at all.
|
||||
bool shadow_contact_hardening_supported();
|
||||
|
||||
// User-facing toggle for shadow contact hardening (PCSS blocker search): sizes each
|
||||
// cascade's penumbra from actual occluder distance instead of the fixed
|
||||
// $Shadow Smoothness Factor: width. Costs an extra raw-depth search per shadowed pixel
|
||||
// (see pcssBlockerSearch() in shadows.sdr), so this is exposed as a setting rather than
|
||||
// being unconditional -- persisted via Graphics.ShadowContactHardening. See
|
||||
// shadow_contact_hardening_enabled() for the flag that actually gates the per-frame cost.
|
||||
extern bool Shadow_contact_hardening_enabled;
|
||||
|
||||
// Whether shadow rendering should actually do the contact-hardening blocker search this
|
||||
// frame, i.e. shadow_contact_hardening_supported() is true AND the user has the option on.
|
||||
// This is the single source of truth -- gate any per-cascade penumbra-scale computation on
|
||||
// this, not on shadow_contact_hardening_supported()/Shadow_contact_hardening_enabled
|
||||
// separately. When false, Shadow_penumbra_scale gets the same negative sentinel as
|
||||
// "unsupported", so the shader takes the pre-contact-hardening fixed-width path at zero
|
||||
// extra cost (see pcssPenumbraRadius() in shadows.sdr).
|
||||
bool shadow_contact_hardening_enabled();
|
||||
|
||||
void shadows_construct_light_frustum(vec3d *min_out, vec3d *max_out, vec3d light_vec, matrix *orient, vec3d *pos, fov_t fov, float aspect, float z_near, float z_far);
|
||||
bool shadows_obj_in_frustum(object *objp, vec3d *min, vec3d *max, matrix *light_orient);
|
||||
void shadows_render_all(fov_t fov, matrix *eye_orient, vec3d *eye_pos,
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "globalincs/pstypes.h"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
using SPIRV_FLOAT_MAT_4x4 = matrix4;
|
||||
using SPIRV_FLOAT_VEC4 = vec4;
|
||||
|
||||
@@ -146,6 +148,14 @@ struct shadow_cascade_static_data {
|
||||
int cascade_count;
|
||||
float rtShadowBiasMin;
|
||||
float rtShadowBiasMax;
|
||||
int rtShadowSampleCount;
|
||||
int rtaoSampleCount;
|
||||
float rtaoRadius;
|
||||
float rtaoStrength;
|
||||
// The scalars above come to exactly 32 bytes, so std140 puts the following mat4
|
||||
// straight after them with no padding. Adding or removing a scalar here changes
|
||||
// that -- keep the C++ layout in lockstep with the shadowCascadeParams block
|
||||
// declarations in deferred-f/main-f/main-v/shadow_map-g/shadow_map-v.sdr.
|
||||
matrix4 shadow_mv_matrix;
|
||||
|
||||
// Position of the view-space origin (the camera) in TLAS-relative space. The RT shadow
|
||||
@@ -173,13 +183,17 @@ struct shadow_cascade_static_data {
|
||||
// main shadow cascades. Set in shadow_cascade_params_bind() (shadows.cpp).
|
||||
int shadow_ray_cull_mask;
|
||||
};
|
||||
static_assert(offsetof(shadow_cascade_static_data, shadow_mv_matrix) == 32,
|
||||
"shadow_cascade_static_data's scalar fields must total exactly 32 bytes to match std140's "
|
||||
"mat4 alignment -- update the shadowCascadeParams block in every .sdr file that declares it "
|
||||
"if this changes.");
|
||||
// Must match the GLSL shadowCascadeParams block's implicit std140 padding
|
||||
// exactly (16 [4 leading scalars] + 64 [matrix4] + 12 [shadow_ray_view_origin]
|
||||
// + 4 [shadow_ray_cull_mask, packed into the vec3's alignment padding] = 96) --
|
||||
// exactly (32 [8 leading scalars] + 64 [matrix4] + 12 [shadow_ray_view_origin]
|
||||
// + 4 [shadow_ray_cull_mask, packed into the vec3's alignment padding] = 112) --
|
||||
// shadow_cascade_params_bind() packs shadow_proj_matrix[] immediately after
|
||||
// this struct via sizeof(), so a mismatch here silently shifts every cascade
|
||||
// matrix in the buffer, in both backends.
|
||||
static_assert(sizeof(shadow_cascade_static_data) == 96, "shadow_cascade_static_data must match the GLSL shadowCascadeParams layout (see comment above)");
|
||||
static_assert(sizeof(shadow_cascade_static_data) == 112, "shadow_cascade_static_data must match the GLSL shadowCascadeParams layout (see comment above)");
|
||||
|
||||
enum class NanoVGShaderType: int32_t {
|
||||
FillGradient = 0, FillImage = 1, Simple = 2, Image = 3
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -118,6 +118,12 @@ public:
|
||||
*/
|
||||
uint32_t getCurrentFrame() const { return m_currentFrame; }
|
||||
|
||||
/**
|
||||
* @brief Get the monotonic frame number set by setCurrentFrame (total frames
|
||||
* rendered, never wraps back -- unlike getCurrentFrame()'s in-flight slot index)
|
||||
*/
|
||||
uint64_t getCurrentFrameNumber() const { return m_currentFrameNumber; }
|
||||
|
||||
/**
|
||||
* @brief Get the Vulkan logical device
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "graphics/matrix.h"
|
||||
#include "graphics/material.h"
|
||||
#include "graphics/grinternal.h"
|
||||
#include "graphics/rtao.h"
|
||||
#include "graphics/shadows.h"
|
||||
#include "lighting/lighting.h"
|
||||
#include "mission/missionparse.h"
|
||||
@@ -374,13 +375,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 +403,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 +417,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);
|
||||
}
|
||||
@@ -545,10 +539,23 @@ void vulkan_deferred_lighting_finish()
|
||||
auto* stateTracker = getStateTracker();
|
||||
vk::CommandBuffer cmd = stateTracker->getCommandBuffer();
|
||||
|
||||
// 1. End G-buffer render pass
|
||||
// TLAS for RTAO: when shadow rendering is off (Shadow_quality Disabled, or a
|
||||
// frame that skips shadows_render_all entirely), nothing has built this
|
||||
// frame's TLAS yet -- request it here. No-op when the shadow path already
|
||||
// built it (buildTlas()'s per-frame guard). Acceleration-structure builds
|
||||
// must be recorded outside a render pass, so this ends the G-buffer pass and
|
||||
// clears it from the state tracker -- which is why step 1 below only ends
|
||||
// the pass when it is still active.
|
||||
if (rtao_enabled()) {
|
||||
vulkan_build_shadow_tlas();
|
||||
}
|
||||
|
||||
// 1. End G-buffer render pass (unless the RTAO TLAS build above already did)
|
||||
// All 6 color attachments → eShaderReadOnlyOptimal
|
||||
// Depth → eDepthStencilAttachmentOptimal
|
||||
cmd.endRenderPass();
|
||||
if (stateTracker->getCurrentRenderPass()) {
|
||||
cmd.endRenderPass();
|
||||
}
|
||||
|
||||
// 2. Copy emissive → composite (the emissive data becomes the base for light accumulation)
|
||||
// Emissive → eShaderReadOnlyOptimal (done), composite → eColorAttachmentOptimal (for light accum)
|
||||
@@ -959,6 +966,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 +1021,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 +1060,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);
|
||||
|
||||
@@ -15,10 +15,17 @@ static constexpr DescriptorBindingTemplate s_globalBindings[] = {
|
||||
{GlobalBinding::EnvMap, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, vk::ImageViewType::eCube},
|
||||
{GlobalBinding::IrradianceMap, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, vk::ImageViewType::eCube},
|
||||
{GlobalBinding::ShadowCascadeParams, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||
{GlobalBinding::ShadowMapRaw, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, vk::ImageViewType::e2DArray},
|
||||
};
|
||||
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 +34,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 +83,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 +116,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 +149,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 +200,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 +331,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 +363,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 +396,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 +480,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);
|
||||
|
||||
Loaded 100 of 267 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user