mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-12 16:17:44 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b88858f63 |
No files matched your search
@@ -150,7 +150,6 @@ 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" OFF)
|
||||
|
||||
OPTION(FSO_BUILD_WITH_VULKAN "Enable compilation of the Vulkan renderer" ON)
|
||||
OPTION(FSO_BUILD_WITH_FFX "Enable AMD FSR in the Vulkan renderer. Downloads AMD's binaries at configure time" ON)
|
||||
|
||||
if(NOT FSO_BUILD_WITH_OPENGL AND FSO_BUILD_WITH_OPENGL_ES)
|
||||
# Disable GLES if not building with OpenGL
|
||||
@@ -195,7 +194,6 @@ mark_as_advanced(FORCE FSO_BUILD_WITH_OPENGL)
|
||||
mark_as_advanced(FORCE FSO_BUILD_WITH_OPENGL_DEBUG)
|
||||
mark_as_advanced(FORCE FSO_BUILD_WITH_OPENGL_ES)
|
||||
mark_as_advanced(FORCE FSO_BUILD_WITH_VULKAN)
|
||||
mark_as_advanced(FORCE FSO_BUILD_WITH_FFX)
|
||||
mark_as_advanced(FORCE FSO_BUILD_WITH_OPENXR)
|
||||
mark_as_advanced(FORCE FSO_BUILD_WITH_OPENXR_DEBUG)
|
||||
|
||||
|
||||
@@ -111,18 +111,6 @@ MACRO(COPY_FILES_TO_TARGET _target)
|
||||
FOREACH(file IN LISTS TARGET_COPY_FILES)
|
||||
COPY_FILE_TO_TARGET("${_target}" "${file}")
|
||||
ENDFOREACH(file)
|
||||
|
||||
# Third party licenses that have to accompany the copied binaries
|
||||
get_property(_ffx_license GLOBAL PROPERTY FSO_FFX_LICENSE_FILE)
|
||||
if (_ffx_license)
|
||||
ADD_CUSTOM_COMMAND(
|
||||
TARGET ${_target} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "$<TARGET_FILE_DIR:${_target}>/license"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_ffx_license}" "$<TARGET_FILE_DIR:${_target}>/license/amd_ffx.txt"
|
||||
COMMENT "copying AMD FidelityFX license..."
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
ENDMACRO(COPY_FILES_TO_TARGET)
|
||||
|
||||
macro(_clear_old_libraries_state)
|
||||
|
||||
@@ -115,11 +115,6 @@ if (FSO_BUILD_WITH_VULKAN)
|
||||
target_compile_definitions(code PUBLIC WITH_VULKAN VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1 VK_NO_PROTOTYPES)
|
||||
target_include_directories(code PUBLIC Vulkan::Headers)
|
||||
target_link_libraries(code PUBLIC VulkanMemoryAllocator)
|
||||
# AMD only ships 64-bit runtimes, and 32-bit Vulkan handles aren't the pointers its API takes
|
||||
if (FSO_BUILD_WITH_FFX AND IS_64BIT)
|
||||
target_compile_definitions(code PRIVATE WITH_FFX)
|
||||
target_link_libraries(code PRIVATE ffx_portable)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(shaders.cmake)
|
||||
|
||||
+6
-25
@@ -25,7 +25,6 @@
|
||||
#include "io/key.h"
|
||||
#include "io/timer.h"
|
||||
#include "jpgutils/jpgutils.h"
|
||||
#include "mod_table/mod_table.h"
|
||||
#include "network/multiutil.h"
|
||||
#include "parse/parselo.h"
|
||||
#include "pcxutils/pcxutils.h"
|
||||
@@ -884,16 +883,6 @@ 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, "");
|
||||
@@ -1372,20 +1361,12 @@ bool bm_load_and_parse_eff(const char *filename, int dir_type, int *nframes, int
|
||||
int frames = 0, fps = 30, keyframe = 0;
|
||||
char ext[8];
|
||||
BM_TYPE c_type = BM_TYPE_NONE;
|
||||
char file_text[1024];
|
||||
char file_text_raw[1024];
|
||||
|
||||
memset(ext, 0, sizeof(ext));
|
||||
|
||||
// cf_find_file_location reports the stored size, which is smaller than the text for a compressed file
|
||||
CFILE* eff_cfp = cfopen(filename, "rb", dir_type);
|
||||
const size_t file_len = (eff_cfp != nullptr) ? static_cast<size_t>(std::max(cfilelength(eff_cfp), 0)) : 0;
|
||||
if (eff_cfp != nullptr) {
|
||||
cfclose(eff_cfp);
|
||||
}
|
||||
|
||||
// encrypted text unpacks 7 bytes into 8, and outside Unicode mode processing can turn one character into two
|
||||
const size_t raw_len = file_len + file_len / 7 + 16;
|
||||
SCP_vector<char> file_text_raw(raw_len, '\0');
|
||||
SCP_vector<char> file_text(Unicode_text_mode ? raw_len : raw_len * 2, '\0');
|
||||
memset(file_text, 0, sizeof(file_text));
|
||||
memset(file_text_raw, 0, sizeof(file_text_raw));
|
||||
|
||||
// pause anything that may happen to be parsing right now
|
||||
pause_parse();
|
||||
@@ -1393,8 +1374,8 @@ bool bm_load_and_parse_eff(const char *filename, int dir_type, int *nframes, int
|
||||
try
|
||||
{
|
||||
// now start parsing the EFF
|
||||
read_file_text(filename, dir_type, file_text.data(), file_text_raw.data());
|
||||
reset_parse(file_text.data());
|
||||
read_file_text(filename, dir_type, file_text, file_text_raw);
|
||||
reset_parse(file_text);
|
||||
|
||||
required_string("$Type:");
|
||||
stuff_string(ext, F_NAME, sizeof(ext));
|
||||
|
||||
@@ -436,13 +436,6 @@ 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()
|
||||
*
|
||||
|
||||
@@ -1199,10 +1199,6 @@ bool cam_set_camera(camid cid)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Autopilot and scripts set the same camera again every frame
|
||||
if (!(Viewer_mode & VM_FREECAMERA) || Current_camera.getSignature() != cid.getSignature()) {
|
||||
gr_reset_temporal_history();
|
||||
}
|
||||
Viewer_mode |= VM_FREECAMERA;
|
||||
Current_camera = cid;
|
||||
|
||||
|
||||
@@ -354,7 +354,6 @@ cmdline_parm deprecated_fxaa_preset_arg("-fxaa_preset", "FXAA quality (0-2), req
|
||||
cmdline_parm deprecated_smaa_arg("-smaa", nullptr, AT_NONE);
|
||||
cmdline_parm deprecated_smaa_preset_arg("-smaa_preset", "SMAA quality (0-3), requires post-processing and -smaa", AT_INT);
|
||||
cmdline_parm msaa_enabled_arg("-msaa", nullptr, AT_INT);
|
||||
cmdline_parm fsr_mode_arg("-fsr", "AMD FSR: 0 off, 1 AA only, 2 upscale to the window resolution. Vulkan only", AT_INT);
|
||||
cmdline_parm fb_explosions_arg("-fb_explosions", NULL, AT_NONE);
|
||||
cmdline_parm fb_thrusters_arg("-fb_thrusters", NULL, AT_NONE);
|
||||
cmdline_parm shadow_quality_arg("-shadow_quality", NULL, AT_INT);
|
||||
@@ -2128,16 +2127,6 @@ bool SetCmdlineParams()
|
||||
post_process_aa_preset_arg.found() ? "-aa_preset" : "-aa");
|
||||
}
|
||||
|
||||
if (fsr_mode_arg.found()) {
|
||||
const int mode = fsr_mode_arg.get_int();
|
||||
if (mode >= static_cast<int>(FsrMode::Off) && mode <= static_cast<int>(FsrMode::Upscale)) {
|
||||
// What of this can run is only known once the renderer is up; gr_init settles it on the closest offered setting
|
||||
Gr_fsr_mode = static_cast<FsrMode>(mode);
|
||||
Gr_fsr_major_version = 0;
|
||||
options::OptionsManager::instance()->setOverride("Graphics.FSR", std::unique_ptr<json_t>(json_integer(mode)), "-fsr");
|
||||
}
|
||||
}
|
||||
|
||||
if (msaa_enabled_arg.found()) {
|
||||
Cmdline_msaa_enabled = msaa_enabled_arg.get_int();
|
||||
switch (Cmdline_msaa_enabled) {
|
||||
|
||||
@@ -1273,7 +1273,6 @@ void debris_render(object * obj, model_draw_list *scene)
|
||||
}
|
||||
|
||||
model_render_params render_info;
|
||||
render_info.set_object_number(OBJ_INDEX(obj));
|
||||
|
||||
if ( db->is_hull ) {
|
||||
MONITOR_INC(NumHullDebrisRend, 1);
|
||||
|
||||
@@ -10,23 +10,13 @@ layout(location=0) out vec4 fragOut0;
|
||||
#ifdef VULKAN
|
||||
layout (location = 0) in vec4 fragTexCoord;
|
||||
layout (location = 1) in vec4 fragColor;
|
||||
layout (location = 2) in vec4 fragVelocityCurClip;
|
||||
layout (location = 3) in vec4 fragVelocityPrevClip;
|
||||
layout (location = 4) in float fragHasMotion;
|
||||
|
||||
layout (location = 6) out vec4 fragOut6;
|
||||
|
||||
layout (set = 1, binding = 1) uniform sampler2DArray baseMap;
|
||||
|
||||
layout (set = 2, binding = 0, std140) uniform genericData {
|
||||
vec4 color;
|
||||
float intensity;
|
||||
mat4 velocityViewProj;
|
||||
mat4 velocityPrevViewProj;
|
||||
vec2 velocityUvScale;
|
||||
};
|
||||
|
||||
#include "motion-vectors.sdr"
|
||||
#else
|
||||
in vec4 fragTexCoord;
|
||||
in vec4 fragColor;
|
||||
@@ -48,7 +38,4 @@ void main()
|
||||
vec4 blendColor = vec4(srgb_to_linear(fragColor.rgb), fragColor.a);
|
||||
|
||||
fragOut0 = baseColor * blendColor * intensity;
|
||||
#ifdef VULKAN
|
||||
fragOut6 = effect_motion(fragVelocityCurClip, fragVelocityPrevClip, fragHasMotion, velocityUvScale, fragOut0);
|
||||
#endif
|
||||
}
|
||||
@@ -6,13 +6,9 @@ precision highp float;
|
||||
layout (location = 0) in vec4 vertPosition;
|
||||
layout (location = 1) in vec4 vertColor;
|
||||
layout (location = 2) in vec4 vertTexCoord;
|
||||
layout (location = 12) in vec4 vertMotion;
|
||||
|
||||
layout (location = 0) out vec4 fragTexCoord;
|
||||
layout (location = 1) out vec4 fragColor;
|
||||
layout (location = 2) out vec4 fragVelocityCurClip;
|
||||
layout (location = 3) out vec4 fragVelocityPrevClip;
|
||||
layout (location = 4) out float fragHasMotion;
|
||||
|
||||
layout (set = 2, binding = 1, std140) uniform matrixData {
|
||||
mat4 modelViewMatrix;
|
||||
@@ -22,9 +18,6 @@ layout (set = 2, binding = 1, std140) uniform matrixData {
|
||||
layout (set = 2, binding = 0, std140) uniform genericData {
|
||||
vec4 color;
|
||||
float intensity;
|
||||
mat4 velocityViewProj;
|
||||
mat4 velocityPrevViewProj;
|
||||
vec2 velocityUvScale;
|
||||
};
|
||||
#else
|
||||
in vec4 vertPosition;
|
||||
@@ -50,10 +43,4 @@ void main()
|
||||
fragColor = vertColor * color;
|
||||
gl_Position = projMatrix * modelViewMatrix * vertPosition;
|
||||
fragTexCoord = vertTexCoord;
|
||||
#ifdef VULKAN
|
||||
// Batched vertices are in world space
|
||||
fragVelocityCurClip = velocityViewProj * vec4(vertPosition.xyz, 1.0);
|
||||
fragVelocityPrevClip = velocityPrevViewProj * vec4(vertPosition.xyz - vertMotion.xyz, 1.0);
|
||||
fragHasMotion = vertMotion.w;
|
||||
#endif
|
||||
}
|
||||
@@ -4,7 +4,6 @@ precision highp float;
|
||||
precision highp int;
|
||||
precision highp sampler2D;
|
||||
precision highp samplerCube;
|
||||
precision highp sampler2DArray;
|
||||
precision highp sampler2DArrayShadow;
|
||||
#endif
|
||||
|
||||
@@ -27,14 +26,13 @@ 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
|
||||
|
||||
#if defined(RT_SHADOWS) || defined(RTAO)
|
||||
#ifdef RT_SHADOWS
|
||||
layout(set = 0, binding = 5) uniform accelerationStructureEXT shadow_tlas;
|
||||
#endif
|
||||
#else
|
||||
@@ -43,7 +41,6 @@ uniform sampler2D NormalBuffer;
|
||||
uniform sampler2D PositionBuffer;
|
||||
uniform sampler2D SpecBuffer;
|
||||
uniform sampler2DArrayShadow shadow_map;
|
||||
uniform sampler2DArray shadow_map_raw;
|
||||
|
||||
#ifdef ENV_MAP
|
||||
uniform samplerCube sEnvmap;
|
||||
@@ -62,20 +59,28 @@ uniform globalDeferredData {
|
||||
float invScreenWidth;
|
||||
float invScreenHeight;
|
||||
float nearPlane;
|
||||
int rtaoSampleCount;
|
||||
|
||||
// The ray-tracing inputs of this pass, for raytraced shadows and RTAO. See
|
||||
// deferred_global_data in uniform_structs.h.
|
||||
vec3 rayViewOrigin;
|
||||
int rayCullMask;
|
||||
|
||||
float rayBiasMin;
|
||||
float rayBiasMax;
|
||||
float rtaoRadius;
|
||||
float rtaoStrength;
|
||||
};
|
||||
|
||||
#include "shadow_cascade_params.sdr" //! #include "shadow_cascade_params.sdr"
|
||||
#ifdef VULKAN
|
||||
layout(set = 0, binding = 6, std140)
|
||||
#else
|
||||
layout(std140)
|
||||
#endif
|
||||
uniform shadowCascadeParams {
|
||||
int cascade_offset;
|
||||
int cascade_count;
|
||||
float rtShadowBiasMin;
|
||||
float rtShadowBiasMax;
|
||||
|
||||
mat4 shadow_mv_matrix;
|
||||
|
||||
// See shadow_cascade_static_data (uniform_structs.h).
|
||||
vec3 shadow_ray_view_origin;
|
||||
int shadow_ray_cull_mask;
|
||||
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];
|
||||
};
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(set = 2, binding = 1, std140)
|
||||
@@ -298,18 +303,6 @@ 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 + rayViewOrigin;
|
||||
vec3 worldNormal = normalize((inv_view_matrix * vec4(normal, 0.0)).xyz);
|
||||
float rtaoBias = computeRtShadowBias(length(position), rayBiasMin, rayBiasMax);
|
||||
ao *= traceAmbientOcclusion(shadow_tlas, worldPos, worldNormal,
|
||||
rtaoRadius, rtaoStrength, rtaoSampleCount, rtaoBias, uint(rayCullMask), gl_FragCoord.xy);
|
||||
}
|
||||
#endif
|
||||
fragmentColor.rgb = diffuseLightColor * diffColor * ao;
|
||||
|
||||
#ifdef ENV_MAP
|
||||
@@ -329,22 +322,15 @@ void main()
|
||||
|
||||
if (enable_shadows != 0) {
|
||||
#ifdef RT_SHADOWS
|
||||
// Ray origin in TLAS-relative space; see deferred_global_data (uniform_structs.h).
|
||||
// Ray origin in TLAS-relative space; see shadow_cascade_static_data (uniform_structs.h).
|
||||
// Only the rotation of inv_view_matrix is used: its translation is the (large) camera
|
||||
// world position, which rayViewOrigin supplies already rebased onto the TLAS.
|
||||
vec3 worldPos = mat3(inv_view_matrix) * position + rayViewOrigin;
|
||||
// world position, which shadow_ray_view_origin supplies already rebased onto the TLAS.
|
||||
vec3 worldPos = mat3(inv_view_matrix) * position + shadow_ray_view_origin;
|
||||
vec3 worldNormal = normalize((inv_view_matrix * vec4(normal, 0.0)).xyz);
|
||||
vec3 worldLightDir = normalize((inv_view_matrix * vec4(lightDir, 0.0)).xyz);
|
||||
|
||||
float rtShadowBias = computeRtShadowBias(length(position), rayBiasMin, rayBiasMax);
|
||||
// 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(rayCullMask), coneTan, rtShadowSampleCount, gl_FragCoord.xy);
|
||||
float rtShadowBias = computeRtShadowBias(length(position), rtShadowBiasMin, rtShadowBiasMax);
|
||||
attenuation *= traceShadowRay(shadow_tlas, worldPos, worldNormal, worldLightDir, lightDist, rtShadowBias, uint(shadow_ray_cull_mask));
|
||||
#else
|
||||
vec4 fragShadowPos = shadow_mv_matrix * inv_view_matrix * vec4(position, 1.0);
|
||||
vec4 fragShadowUV[NUM_SHADOW_CASCADES];
|
||||
@@ -352,7 +338,7 @@ void main()
|
||||
fragShadowUV[i] = transformToShadowMap(shadow_proj_matrix[i], i, fragShadowPos);
|
||||
}
|
||||
|
||||
attenuation *= getShadowValue(shadow_map, shadow_map_raw, -position.z, fragShadowUV, cascade_distances, smoothness_factors, penumbra_scale, cascade_offset, cascade_count);
|
||||
attenuation *= getShadowValue(shadow_map, -position.z, fragShadowUV, cascade_distances, smoothness_factors, cascade_offset, cascade_count);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -15,11 +15,6 @@ 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 = 4) in vec4 fragVelocityCurClip;
|
||||
layout(location = 5) in vec4 fragVelocityPrevClip;
|
||||
layout(location = 6) in float fragHasMotion;
|
||||
|
||||
layout(location = 6) out vec4 fragOut6;
|
||||
|
||||
layout(set = 1, binding = 1) uniform sampler2DArray baseMap;
|
||||
layout(set = 1, binding = 4) uniform sampler2D depthMap;
|
||||
@@ -46,17 +41,8 @@ uniform genericData {
|
||||
int linear_depth;
|
||||
int srgb;
|
||||
int blend_alpha;
|
||||
#ifdef VULKAN
|
||||
mat4 velocityViewProj;
|
||||
mat4 velocityPrevViewProj;
|
||||
vec2 velocityUvScale;
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef VULKAN
|
||||
#include "motion-vectors.sdr"
|
||||
#endif
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
@@ -67,9 +53,6 @@ void main()
|
||||
float offset_len = length(offset);
|
||||
if ( offset_len > fragRadius ) {
|
||||
fragOut0 = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
#ifdef VULKAN
|
||||
fragOut6 = vec4(0.0);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
vec2 depthCoord = vec2(gl_FragCoord.x / window_width, gl_FragCoord.y / window_height );
|
||||
@@ -97,7 +80,4 @@ void main()
|
||||
fragmentColor.rgb *= (srgb == 1) ? 1.5 : 1.0;
|
||||
fragmentColor = (blend_alpha == 1) ? vec4(fragmentColor.rgb, fragmentColor.a * intensity) : vec4(fragmentColor.rgb * intensity, fragmentColor.a);
|
||||
fragOut0 = max(fragmentColor, vec4(0.0));
|
||||
#ifdef VULKAN
|
||||
fragOut6 = effect_motion(fragVelocityCurClip, fragVelocityPrevClip, fragHasMotion, velocityUvScale, fragOut0);
|
||||
#endif
|
||||
}
|
||||
@@ -7,28 +7,11 @@ layout(location = 0) in vec4 vertPosition;
|
||||
layout(location = 1) in vec4 vertColor;
|
||||
layout(location = 2) in vec4 vertTexCoord;
|
||||
layout(location = 6) in float vertRadius;
|
||||
layout(location = 12) in vec4 vertMotion;
|
||||
|
||||
layout(location = 0) out vec4 fragTexCoord;
|
||||
layout(location = 1) out vec4 fragColor;
|
||||
layout(location = 2) out float fragRadius;
|
||||
layout(location = 3) out vec4 fragPosition;
|
||||
layout(location = 4) out vec4 fragVelocityCurClip;
|
||||
layout(location = 5) out vec4 fragVelocityPrevClip;
|
||||
layout(location = 6) out float fragHasMotion;
|
||||
|
||||
layout(std140, set = 2, binding = 0) uniform genericData {
|
||||
float window_width;
|
||||
float window_height;
|
||||
float nearZ;
|
||||
float farZ;
|
||||
int linear_depth;
|
||||
int srgb;
|
||||
int blend_alpha;
|
||||
mat4 velocityViewProj;
|
||||
mat4 velocityPrevViewProj;
|
||||
vec2 velocityUvScale;
|
||||
};
|
||||
#else
|
||||
in vec4 vertPosition;
|
||||
in vec4 vertTexCoord;
|
||||
@@ -74,10 +57,4 @@ void main()
|
||||
fragTexCoord = vec4(vertTexCoord.xyz, 0.0);
|
||||
fragColor = vertColor;
|
||||
#endif
|
||||
#ifdef VULKAN
|
||||
// Batched vertices are in world space
|
||||
fragVelocityCurClip = velocityViewProj * vec4(vertPosition.xyz, 1.0);
|
||||
fragVelocityPrevClip = velocityPrevViewProj * vec4(vertPosition.xyz - vertMotion.xyz, 1.0);
|
||||
fragHasMotion = vertMotion.w;
|
||||
#endif
|
||||
}
|
||||
@@ -98,21 +98,28 @@ uniform modelData {
|
||||
int sMiscmapIndex;
|
||||
float alphaMult;
|
||||
int flags;
|
||||
|
||||
#ifdef VULKAN
|
||||
mat4 prevModelMatrix;
|
||||
mat4 velocityViewProj;
|
||||
mat4 velocityPrevViewProj;
|
||||
vec2 velocityUvScale;
|
||||
int prev_transform_stride;
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef VULKAN
|
||||
#include "motion-vectors.sdr"
|
||||
layout(set = 0, binding = 6, std140)
|
||||
#else
|
||||
layout(std140)
|
||||
#endif
|
||||
uniform shadowCascadeParams {
|
||||
int cascade_offset;
|
||||
int cascade_count;
|
||||
float rtShadowBiasMin;
|
||||
float rtShadowBiasMax;
|
||||
|
||||
#include "shadow_cascade_params.sdr" //! #include "shadow_cascade_params.sdr"
|
||||
mat4 shadow_mv_matrix;
|
||||
|
||||
// See shadow_cascade_static_data (uniform_structs.h).
|
||||
vec3 shadow_ray_view_origin;
|
||||
int shadow_ray_cull_mask;
|
||||
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];
|
||||
};
|
||||
|
||||
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
@@ -131,7 +138,6 @@ 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
|
||||
@@ -144,8 +150,6 @@ layout(location = 0) in VertexOutput {
|
||||
vec4 texCoord;
|
||||
mat3 tangentMatrix;
|
||||
float fogDist;
|
||||
vec4 velocityCurClip;
|
||||
vec4 velocityPrevClip;
|
||||
|
||||
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_SHADOWS
|
||||
vec4 shadowUV[NUM_SHADOW_CASCADES];
|
||||
@@ -157,7 +161,6 @@ layout(location = 0) in VertexOutput {
|
||||
#endif
|
||||
} vertIn;
|
||||
|
||||
layout(location = 6) out vec4 fragOut6;
|
||||
#else
|
||||
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_DIFFUSE
|
||||
uniform sampler2DArray sBasemap;
|
||||
@@ -180,7 +183,6 @@ 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 {
|
||||
@@ -265,11 +267,7 @@ 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);
|
||||
// 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);
|
||||
shadow = traceShadowRay(shadow_tlas, worldPos, worldNormal, worldSunDir, RT_SHADOW_MAX_DISTANCE, rtShadowBias, uint(shadow_ray_cull_mask));
|
||||
++shadowedDirectionalCount;
|
||||
} else {
|
||||
shadow = 1.0;
|
||||
@@ -441,7 +439,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, shadow_map_raw, -vertIn.position.z, vertIn.shadowUV, cascade_distances, smoothness_factors, penumbra_scale, cascade_offset, cascade_count);
|
||||
shadow = getShadowValue(shadow_map, -vertIn.position.z, vertIn.shadowUV, cascade_distances, smoothness_factors, 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);
|
||||
@@ -541,16 +539,6 @@ void main()
|
||||
|
||||
fragOut0 = baseColor;
|
||||
|
||||
#ifdef VULKAN
|
||||
// Alpha selects whether this replaces the motion already there: faint transparent surfaces keep what's behind them.
|
||||
vec2 velocity = motion_uv(vertIn.velocityCurClip, vertIn.velocityPrevClip, velocityUvScale);
|
||||
float velocityAlpha = baseColor.a >= 0.5 ? 1.0 : 0.0;
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_DEFERRED
|
||||
velocityAlpha = 1.0;
|
||||
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_DEFERRED
|
||||
fragOut6 = vec4(velocity, motion_depth(vertIn.velocityCurClip), velocityAlpha);
|
||||
#endif
|
||||
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_DEFERRED
|
||||
fragOut1 = vec4(vertIn.position.xy, compress_depth_value(vertIn.position.z), aoFactors.x);
|
||||
fragOut2 = vec4(normal, glossData);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
precision highp samplerBuffer;
|
||||
precision highp sampler2DArray;
|
||||
precision highp sampler2DArrayShadow;
|
||||
#else
|
||||
#extension GL_ARB_gpu_shader5: enable
|
||||
@@ -114,17 +113,28 @@ uniform modelData {
|
||||
float alphaMult;
|
||||
|
||||
int flags;
|
||||
|
||||
#ifdef VULKAN
|
||||
mat4 prevModelMatrix;
|
||||
mat4 velocityViewProj;
|
||||
mat4 velocityPrevViewProj;
|
||||
vec2 velocityUvScale;
|
||||
int prev_transform_stride;
|
||||
#endif
|
||||
};
|
||||
|
||||
#include "shadow_cascade_params.sdr" //! #include "shadow_cascade_params.sdr"
|
||||
#ifdef VULKAN
|
||||
layout(set = 0, binding = 6, std140)
|
||||
#else
|
||||
layout(std140)
|
||||
#endif
|
||||
uniform shadowCascadeParams {
|
||||
int cascade_offset;
|
||||
int cascade_count;
|
||||
float rtShadowBiasMin;
|
||||
float rtShadowBiasMax;
|
||||
|
||||
mat4 shadow_mv_matrix;
|
||||
|
||||
// See shadow_cascade_static_data (uniform_structs.h). Unused here; declared for block layout.
|
||||
vec3 shadow_ray_view_origin;
|
||||
int shadow_ray_cull_mask;
|
||||
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];
|
||||
};
|
||||
|
||||
#ifdef VULKAN
|
||||
// Transform buffer for batched submodel rendering
|
||||
@@ -140,8 +150,6 @@ layout(location = 0) out VertexOutput {
|
||||
vec4 texCoord;
|
||||
mat3 tangentMatrix;
|
||||
float fogDist;
|
||||
vec4 velocityCurClip;
|
||||
vec4 velocityPrevClip;
|
||||
|
||||
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_SHADOWS
|
||||
vec4 shadowUV[NUM_SHADOW_CASCADES];
|
||||
@@ -198,9 +206,6 @@ void main()
|
||||
vec3 normal;
|
||||
vec4 texCoord;
|
||||
mat4 orient = mat4(1.0);
|
||||
#ifdef VULKAN
|
||||
mat4 prevOrient = mat4(1.0);
|
||||
#endif
|
||||
bool clipModel = false;
|
||||
|
||||
// Transform loading
|
||||
@@ -210,11 +215,6 @@ void main()
|
||||
orient = transformBuf.transforms[buffer_matrix_offset + id];
|
||||
clipModel = (orient[3].w >= 0.9);
|
||||
orient[3].w = 1.0;
|
||||
prevOrient = orient;
|
||||
if (prev_transform_stride > 0) {
|
||||
prevOrient = transformBuf.transforms[buffer_matrix_offset + prev_transform_stride + id];
|
||||
prevOrient[3].w = 1.0;
|
||||
}
|
||||
#else
|
||||
getModelTransform(orient, clipModel, int(vertModelID), buffer_matrix_offset);
|
||||
#endif
|
||||
@@ -234,10 +234,6 @@ void main()
|
||||
normal = normalize(mat3(modelViewMatrix) * mat3(orient) * vertNormal);
|
||||
position = modelViewMatrix * orient * vertex;
|
||||
gl_Position = projMatrix * position;
|
||||
#ifdef VULKAN
|
||||
vertOut.velocityCurClip = velocityViewProj * modelMatrix * orient * vertex;
|
||||
vertOut.velocityPrevClip = velocityPrevViewProj * prevModelMatrix * prevOrient * vertex;
|
||||
#endif
|
||||
#prereplace IF_FLAG MODEL_SDR_FLAG_SHADOWS
|
||||
vec4 shadowPos = shadow_mv_matrix * modelMatrix * orient * vertPosition;
|
||||
vertOut.shadowPos = shadowPos;
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
layout(location = 0) out vec4 fragOut0;
|
||||
layout(location = 1) out vec4 fragOut1;
|
||||
|
||||
// Scene depth, and the drawn geometry's motion with its inverse view depth
|
||||
layout(set = 1, binding = 1) uniform sampler2D inputs[2];
|
||||
|
||||
layout(std140, set = 2, binding = 0) uniform genericData {
|
||||
mat4 reprojection;
|
||||
vec4 viewportRect; // xy = offset, zw = size, as fractions of the image
|
||||
float sceneNear;
|
||||
float cockpitNear;
|
||||
float farPlane;
|
||||
float pad;
|
||||
};
|
||||
|
||||
void main()
|
||||
{
|
||||
// All depth goes on the cockpit's projection: its nearer plane can express every scene distance. That depth is
|
||||
// linear in the inverse view depth.
|
||||
float depthScale = farPlane / (farPlane - cockpitNear);
|
||||
|
||||
// Whatever drew its own motion brought its depth along. That keeps the two matched where the scene's depth
|
||||
// doesn't show what drew the motion, like the cockpit and the player's hull, which are drawn over a cleared depth.
|
||||
vec3 drawnMotion = texture(inputs[1], fragTexCoord).xyz;
|
||||
if (abs(drawnMotion.x) < 100.0) {
|
||||
fragOut0 = vec4(drawnMotion.xy, 0.0, 0.0);
|
||||
fragOut1 = vec4(clamp(depthScale * (1.0 - cockpitNear * drawnMotion.z), 0.0, 1.0), 0.0, 0.0, 0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Anything else still holds the clear value, so only the camera moved it.
|
||||
float sceneDepth = texture(inputs[0], fragTexCoord).r;
|
||||
float viewDepth = farPlane * sceneNear / (farPlane - sceneDepth * (farPlane - sceneNear));
|
||||
fragOut1 = vec4(depthScale * (1.0 - cockpitNear / viewDepth), 0.0, 0.0, 0.0);
|
||||
|
||||
// The projection maps +Y up onto the top of the image through a negative viewport height.
|
||||
vec2 viewportUv = (fragTexCoord - viewportRect.xy) / viewportRect.zw;
|
||||
vec4 ndc = vec4(viewportUv.x * 2.0 - 1.0, 1.0 - viewportUv.y * 2.0, sceneDepth, 1.0);
|
||||
|
||||
vec4 previous = reprojection * ndc;
|
||||
// Behind last frame's camera, or no previous camera yet
|
||||
if (previous.w <= 0.0) {
|
||||
fragOut0 = vec4(0.0);
|
||||
return;
|
||||
}
|
||||
previous.xy /= previous.w;
|
||||
|
||||
vec2 previousViewportUv = vec2(previous.x * 0.5 + 0.5, 0.5 - previous.y * 0.5);
|
||||
vec2 previousUv = previousViewportUv * viewportRect.zw + viewportRect.xy;
|
||||
|
||||
fragOut0 = vec4(previousUv - fragTexCoord, 0.0, 0.0);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// Screen motion since last frame in UV units, pointing at the previous position. Zero if that was behind the camera.
|
||||
vec2 motion_uv(vec4 curClip, vec4 prevClip, vec2 uvScale)
|
||||
{
|
||||
if (prevClip.w <= 0.0) {
|
||||
return vec2(0.0);
|
||||
}
|
||||
return (prevClip.xy / prevClip.w - curClip.xy / curClip.w) * vec2(0.5, -0.5) * uvScale;
|
||||
}
|
||||
|
||||
// The depth stored with drawn motion: the inverse of the view depth, so that it can't overflow half floats and maps
|
||||
// linearly onto the depth buffer of any projection.
|
||||
float motion_depth(vec4 curClip)
|
||||
{
|
||||
return 1.0 / curClip.w;
|
||||
}
|
||||
|
||||
// Motion vector output of the batched effect shaders, for location 6. Alpha 0 keeps the motion already there, through
|
||||
// the attachment's blend state: effects without movement of their own, and fragments too faint to matter.
|
||||
const float EFFECT_MOTION_MIN_CONTRIBUTION = 0.1;
|
||||
|
||||
vec4 effect_motion(vec4 curClip, vec4 prevClip, float hasMotion, vec2 uvScale, vec4 color)
|
||||
{
|
||||
float contribution = max(max(color.r, color.g), color.b) * color.a;
|
||||
if (hasMotion < 0.5 || contribution < EFFECT_MOTION_MIN_CONTRIBUTION || prevClip.w <= 0.0) {
|
||||
return vec4(0.0);
|
||||
}
|
||||
return vec4(motion_uv(curClip, prevClip, uvScale), motion_depth(curClip), 1.0);
|
||||
}
|
||||
@@ -13,17 +13,15 @@ layout(location = 3) out vec4 fragOut3;
|
||||
layout(location = 4) out vec4 fragOut4;
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(location = 6) out vec4 fragOut6;
|
||||
layout(location = 0) in vec2 fragTexCoord;
|
||||
|
||||
layout(set = 1, binding = 1) uniform sampler2DMS msaaTex[7];
|
||||
layout(set = 1, binding = 1) uniform sampler2DMS msaaTex[6];
|
||||
#define texColor msaaTex[0]
|
||||
#define texPos msaaTex[1]
|
||||
#define texNormal msaaTex[2]
|
||||
#define texSpecular msaaTex[3]
|
||||
#define texEmissive msaaTex[4]
|
||||
#define texDepth msaaTex[5]
|
||||
#define texVelocity msaaTex[6]
|
||||
|
||||
layout(std140, set = 2, binding = 0) uniform genericData {
|
||||
int samples;
|
||||
@@ -210,10 +208,6 @@ void main()
|
||||
vec4 specular = vec4(0);
|
||||
vec4 emissive = vec4(0);
|
||||
float depth = 0.0;
|
||||
#ifdef VULKAN
|
||||
vec3 velocity = vec3(0.0);
|
||||
float velocityWeight = 0.0;
|
||||
#endif
|
||||
|
||||
for(int i = 0; i < SAMPLE_CNT; i++) {
|
||||
vec4 localPos = texelFetch(texPos, texel, i);
|
||||
@@ -232,14 +226,6 @@ void main()
|
||||
emissive += texelFetch(texEmissive, texel, i) * localWeight;
|
||||
depth += texelFetch(texDepth, texel, i).x * localWeight;
|
||||
weight += localWeight;
|
||||
#ifdef VULKAN
|
||||
// Samples still holding the "no motion written" clear value mustn't average into real ones.
|
||||
vec3 localVelocity = texelFetch(texVelocity, texel, i).xyz;
|
||||
if (abs(localVelocity.x) < 100.0) {
|
||||
velocity += localVelocity * localWeight;
|
||||
velocityWeight += localWeight;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
pos /= weight;
|
||||
@@ -251,8 +237,4 @@ void main()
|
||||
fragOut3 = specular / weight;
|
||||
fragOut4 = emissive / weight;
|
||||
gl_FragDepth = depth / weight;
|
||||
#ifdef VULKAN
|
||||
// Alpha 0 keeps what's there: the cockpit's pass mustn't erase the main scene's motion around it.
|
||||
fragOut6 = velocityWeight > 0.0f ? vec4(velocity / velocityWeight, 1.0) : vec4(0.0);
|
||||
#endif
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// The shadowCascadeParams uniform block. All shaders that read the shadow cascades include this
|
||||
// file, so the block has one declaration. The C++ layout is shadow_cascade_static_data in
|
||||
// uniform_structs.h. Change both declarations together.
|
||||
|
||||
#ifdef VULKAN
|
||||
layout(set = 0, binding = 6, std140)
|
||||
#else
|
||||
layout(std140)
|
||||
#endif
|
||||
uniform shadowCascadeParams {
|
||||
int cascade_offset;
|
||||
int cascade_count;
|
||||
float rtShadowBiasMin;
|
||||
float rtShadowBiasMax;
|
||||
int rtShadowSampleCount;
|
||||
// Padding. The scalars must fill 32 bytes, so that shadow_mv_matrix starts on a 16-byte boundary.
|
||||
int shadowParamsPad0;
|
||||
int shadowParamsPad1;
|
||||
int shadowParamsPad2;
|
||||
|
||||
mat4 shadow_mv_matrix;
|
||||
|
||||
// The camera position in the coordinate space of the ray-tracing structure.
|
||||
// See shadow_cascade_static_data in uniform_structs.h.
|
||||
vec3 shadow_ray_view_origin;
|
||||
int shadow_ray_cull_mask;
|
||||
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];
|
||||
};
|
||||
@@ -14,9 +14,21 @@ layout (triangle_strip, max_vertices = 3) out;
|
||||
layout(invocations = NUM_SHADOW_CASCADES) in;
|
||||
#endif
|
||||
|
||||
// OpenGL-only: the Vulkan backend has no geometry-shader stage, so the Vulkan binding in the
|
||||
// include below does not apply here.
|
||||
#include "shadow_cascade_params.sdr" //! #include "shadow_cascade_params.sdr"
|
||||
layout (std140) uniform shadowCascadeParams {
|
||||
int cascade_offset;
|
||||
int cascade_count;
|
||||
float rtShadowBiasMin;
|
||||
float rtShadowBiasMax;
|
||||
|
||||
mat4 shadow_mv_matrix;
|
||||
|
||||
// See shadow_cascade_static_data (uniform_structs.h). Unused here; declared for block layout.
|
||||
vec3 shadow_ray_view_origin;
|
||||
int shadow_ray_cull_mask;
|
||||
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];
|
||||
};
|
||||
|
||||
in VertexOutput {
|
||||
#if !defined(GL_ARB_gpu_shader5) && !defined(GL_EXT_gpu_shader5)
|
||||
|
||||
@@ -39,7 +39,26 @@ uniform shadowMapData {
|
||||
int buffer_matrix_offset;
|
||||
};
|
||||
|
||||
#include "shadow_cascade_params.sdr" //! #include "shadow_cascade_params.sdr"
|
||||
#ifdef VULKAN
|
||||
layout(set = 0, binding = 6, std140)
|
||||
#else
|
||||
layout(std140)
|
||||
#endif
|
||||
uniform shadowCascadeParams {
|
||||
int cascade_offset;
|
||||
int cascade_count;
|
||||
float rtShadowBiasMin;
|
||||
float rtShadowBiasMax;
|
||||
|
||||
mat4 shadow_mv_matrix;
|
||||
|
||||
// See shadow_cascade_static_data (uniform_structs.h). Unused here; declared for block layout.
|
||||
vec3 shadow_ray_view_origin;
|
||||
int shadow_ray_cull_mask;
|
||||
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];
|
||||
};
|
||||
|
||||
#ifdef VULKAN
|
||||
// Transform buffer for batched submodel rendering (matches main-v.sdr)
|
||||
|
||||
@@ -6,47 +6,33 @@
|
||||
#define SAMPLES_PCSS 1
|
||||
#endif
|
||||
|
||||
// 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
|
||||
#define poissonDisc poissonDisc16
|
||||
#else
|
||||
#define poissonDisc poissonDiscCenter
|
||||
#endif
|
||||
|
||||
float samplePoissonPCSS(sampler2DArrayShadow shadow_map, vec4 shadowUV, float maxUVOffset)
|
||||
{
|
||||
#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)
|
||||
);
|
||||
#else
|
||||
vec2 poissonDisc[1] = vec2[](
|
||||
vec2(0.0,0.0)
|
||||
);
|
||||
#endif
|
||||
|
||||
float visibility = 0.0f;
|
||||
for (int i=0; i<SAMPLES_PCSS; i++) {
|
||||
vec4 uv = shadowUV;
|
||||
@@ -56,82 +42,8 @@ float samplePoissonPCSS(sampler2DArrayShadow shadow_map, vec4 shadowUV, float ma
|
||||
return visibility * (1.0f/float(SAMPLES_PCSS));
|
||||
}
|
||||
|
||||
// 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],
|
||||
float getShadowValue(sampler2DArrayShadow shadow_map, 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;
|
||||
@@ -140,19 +52,19 @@ float getShadowValue(sampler2DArrayShadow shadow_map, sampler2DArray shadow_map_
|
||||
}
|
||||
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 / 4][cascade % 4];
|
||||
float cascade_end = cascade_distances[cascade_v][cascade_i];
|
||||
float dist_threshold = (cascade_end - cascade_start) * 0.2;
|
||||
|
||||
if (cascade_end - dist_threshold > depth || cascade >= cascade_offset + cascade_count - 1)
|
||||
return shadowVisibilityForCascade(shadow_map, shadow_map_raw, shadowUV[cascade], cascade,
|
||||
smoothness_factors, penumbra_scale);
|
||||
return samplePoissonPCSS(shadow_map, shadowUV[cascade], smoothness_factors[cascade_v][cascade_i]);
|
||||
|
||||
return mix(
|
||||
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),
|
||||
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]),
|
||||
smoothstep(cascade_end - dist_threshold, cascade_end, depth));
|
||||
}
|
||||
// Raytraced shadow test via inline ray query against the shadow TLAS. Only
|
||||
@@ -167,11 +79,11 @@ float getShadowValue(sampler2DArrayShadow shadow_map, sampler2DArray shadow_map_
|
||||
// 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) || defined(RTAO)
|
||||
#if defined(MODEL_SDR_FLAG_RT_SHADOWS) || defined(RT_SHADOWS)
|
||||
#extension GL_EXT_ray_query : require
|
||||
|
||||
// Ray origin bias grows with distance from the camera, between the rtShadowBiasMin/
|
||||
// rtShadowBiasMax bounds (see the shadowCascadeParams and globalDeferredData blocks, and the Min/Max
|
||||
// rtShadowBiasMax bounds (see shadowCascadeParams uniform block, and the Min/Max
|
||||
// Raytraced Shadow Bias options in shadows.cpp). Empirically, geometry close to the
|
||||
// camera only needs biasMin worth of offset to clear self-intersection acne, while
|
||||
// distant geometry needs more -- consistent with the error source being G-buffer/
|
||||
@@ -188,20 +100,6 @@ float computeRtShadowBias(float cameraDist, float biasMin, float biasMax)
|
||||
return mix(biasMin, biasMax, clamp(cameraDist / RT_SHADOW_BIAS_RAMP_DISTANCE, 0.0, 1.0));
|
||||
}
|
||||
|
||||
// Traces one occlusion ray. traceShadowRay() and traceShadowRayCone() both use this function.
|
||||
// The origin must already include the bias. The direction must be a unit vector. cullMask has
|
||||
// the same meaning as in traceShadowRay(). Returns 1.0 when the ray reaches tMax with no hit, and
|
||||
// 0.0 when it hits an instance.
|
||||
float traceVisibilityRay(accelerationStructureEXT tlas, vec3 origin, vec3 direction, float tMax, uint cullMask)
|
||||
{
|
||||
rayQueryEXT rq;
|
||||
rayQueryInitializeEXT(rq, tlas, gl_RayFlagsOpaqueEXT | gl_RayFlagsTerminateOnFirstHitEXT,
|
||||
cullMask, origin, 0.001, direction, tMax);
|
||||
while (rayQueryProceedEXT(rq)) {}
|
||||
|
||||
return (rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionNoneEXT) ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
// worldPos/worldNormal/worldLightDir must all be in the same world space the
|
||||
// shadow TLAS was built in (see VulkanRaytracingManager::buildTlas()) -- i.e.
|
||||
// FS2Open object-space positions/orientations, not view/eye-space. worldLightDir
|
||||
@@ -219,139 +117,20 @@ float traceVisibilityRay(accelerationStructureEXT tlas, vec3 origin, vec3 direct
|
||||
//
|
||||
// cullMask selects which TLAS instances this ray can hit (Vulkan spec: an instance
|
||||
// is a candidate iff (cullMask & instance.mask) != 0). Callers should pass
|
||||
// the shadow ray cull mask from a uniform block rather than a literal --
|
||||
// shadow_ray_cull_mask (shadowCascadeParams uniform block) rather than a literal --
|
||||
// see SHADOW_RAY_CULL_MASK_EXCLUDE_VIEWER_HULL/TLAS_MASK_VIEWER_HULL (shadows.h)
|
||||
// for how it excludes the viewer ship's own hull outside the cockpit pass.
|
||||
float traceShadowRay(accelerationStructureEXT tlas, vec3 worldPos, vec3 worldNormal, vec3 worldLightDir, float tMax, float bias, uint cullMask)
|
||||
{
|
||||
return traceVisibilityRay(tlas, worldPos + worldNormal * bias, normalize(worldLightDir), tMax, cullMask);
|
||||
}
|
||||
|
||||
// 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). The
|
||||
// rtShadowSampleCount and rtaoSampleCount uniforms 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);
|
||||
rayQueryEXT rq;
|
||||
rayQueryInitializeEXT(rq, tlas, gl_RayFlagsOpaqueEXT | gl_RayFlagsTerminateOnFirstHitEXT,
|
||||
cullMask, origin, 0.001, direction, tMax);
|
||||
while (rayQueryProceedEXT(rq)) {}
|
||||
|
||||
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);
|
||||
visibility += traceVisibilityRay(tlas, origin, sampleDir, tMax, cullMask);
|
||||
}
|
||||
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, uint cullMask, 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,
|
||||
cullMask, 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);
|
||||
return (rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionNoneEXT) ? 1.0 : 0.0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1212,18 +1212,17 @@ void fireball_render(object* obj, model_draw_list *scene)
|
||||
Neb2_fog_visibility_fireball_const + (obj->radius * Neb2_fog_visibility_fireball_scaled_factor));
|
||||
|
||||
g3_transfer_vertex(&p, &obj->pos);
|
||||
const vec3d motion = obj->pos - obj->last_pos;
|
||||
|
||||
switch ( fb->fireball_render_type ) {
|
||||
|
||||
case FIREBALL_MEDIUM_EXPLOSION: {
|
||||
batching_add_volume_bitmap(fb->current_bitmap, &p, fb->orient, obj->radius, alpha, 0.0f, &motion);
|
||||
batching_add_volume_bitmap(fb->current_bitmap, &p, fb->orient, obj->radius, alpha);
|
||||
}
|
||||
break;
|
||||
|
||||
case FIREBALL_LARGE_EXPLOSION: {
|
||||
// Make the big explosions rotate with the viewer.
|
||||
batching_add_volume_bitmap_rotated(fb->current_bitmap, &p, (i2fl(fb->orient)*PI) / 180.0f, obj->radius, alpha, 0.0f, &motion);
|
||||
batching_add_volume_bitmap_rotated(fb->current_bitmap, &p, (i2fl(fb->orient)*PI) / 180.0f, obj->radius, alpha);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -788,8 +788,6 @@ void event_music_level_close()
|
||||
}
|
||||
else
|
||||
audiostream_close_file( Patterns[i].handle, 0 );
|
||||
|
||||
Patterns[i].handle = -1;
|
||||
}
|
||||
} else {
|
||||
// close em all down then
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
#include "model/modelrender.h"
|
||||
#include "libs/jansson.h"
|
||||
#include "options/Option.h"
|
||||
#include "options/OptionsManager.h"
|
||||
#include "osapi/osapi.h"
|
||||
#include "parse/parselo.h"
|
||||
#include "popup/popup.h"
|
||||
@@ -86,8 +85,6 @@ 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),
|
||||
GR_CAPABILITY_ENTRY(FSR),
|
||||
};
|
||||
|
||||
const size_t gr_capabilities_num = sizeof(gr_capabilities) / sizeof(gr_capabilities[0]);
|
||||
@@ -877,123 +874,6 @@ static auto AAOption __UNUSED = options::OptionBuilder<AntiAliasMode>("Graphics.
|
||||
.parser(parse_anti_aliasing_func)
|
||||
.finish();
|
||||
|
||||
FsrMode Gr_fsr_mode = FsrMode::Off;
|
||||
int Gr_fsr_major_version = 0;
|
||||
SCP_vector<SCP_string> Gr_fsr_versions;
|
||||
int Gr_fsr_max_output_size = 0;
|
||||
float Gr_composition_scale = 1.0f;
|
||||
|
||||
static float fsr_window_factor()
|
||||
{
|
||||
if (!Cmdline_window_res.has_value()) {
|
||||
return 1.0f;
|
||||
}
|
||||
const float factor = std::max(i2fl(Cmdline_window_res->first) / i2fl(gr_screen.max_w),
|
||||
i2fl(Cmdline_window_res->second) / i2fl(gr_screen.max_h));
|
||||
if (factor <= 1.0f || std::max(gr_screen.max_w, gr_screen.max_h) * factor > Gr_fsr_max_output_size) {
|
||||
return 1.0f;
|
||||
}
|
||||
return factor;
|
||||
}
|
||||
|
||||
float gr_fsr_upscale_factor(FsrMode mode)
|
||||
{
|
||||
return mode == FsrMode::Upscale ? fsr_window_factor() : 1.0f;
|
||||
}
|
||||
|
||||
static int fsr_setting(FsrMode mode, int major_version)
|
||||
{
|
||||
return mode == FsrMode::Off ? 0 : major_version * 10 + static_cast<int>(mode);
|
||||
}
|
||||
|
||||
static SCP_vector<int> fsr_setting_enumerator()
|
||||
{
|
||||
SCP_vector<int> out{0};
|
||||
|
||||
// Options are also enumerated before gr_init has set up the renderer.
|
||||
if (gr_screen.gf_is_capable && gr_is_capable(gr_capability::CAPABILITY_FSR)) {
|
||||
for (const auto& version : Gr_fsr_versions) {
|
||||
const int major = atoi(version.c_str());
|
||||
out.push_back(fsr_setting(FsrMode::AA, major));
|
||||
if (fsr_window_factor() > 1.0f) {
|
||||
out.push_back(fsr_setting(FsrMode::Upscale, major));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static SCP_string fsr_setting_display(int setting)
|
||||
{
|
||||
const auto mode = static_cast<FsrMode>(setting % 10);
|
||||
if (mode == FsrMode::Off) {
|
||||
return "Off";
|
||||
}
|
||||
|
||||
SCP_string label = "FSR";
|
||||
for (const auto& name : Gr_fsr_versions) {
|
||||
if (atoi(name.c_str()) == setting / 10) {
|
||||
label += " " + name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return label + (mode == FsrMode::AA ? " AA" : " Upscale");
|
||||
}
|
||||
|
||||
static void fsr_apply_setting(int setting)
|
||||
{
|
||||
const int mode = setting % 10;
|
||||
Gr_fsr_mode = mode <= static_cast<int>(FsrMode::Upscale) ? static_cast<FsrMode>(mode) : FsrMode::Off;
|
||||
Gr_fsr_major_version = setting / 10;
|
||||
}
|
||||
|
||||
// -fsr only names a mode, and what of it can run is only known once the renderer is up. Settle it on the closest
|
||||
// setting that is offered (the newest version, Upscale dropping to AA, or Off), so that both the FSR state and the
|
||||
// locked option show what actually runs.
|
||||
static void fsr_resolve_cmdline_setting()
|
||||
{
|
||||
const auto options = options::OptionsManager::instance();
|
||||
if (options->getOverrideReason("Graphics.FSR") != "-fsr") {
|
||||
return;
|
||||
}
|
||||
|
||||
int resolved = 0;
|
||||
if (Gr_fsr_mode != FsrMode::Off) {
|
||||
// The offered settings are ordered newest version first
|
||||
const auto offered = fsr_setting_enumerator();
|
||||
const auto newest = [&offered](FsrMode mode) {
|
||||
const auto it = std::find_if(offered.begin(), offered.end(), [mode](int setting) { return setting % 10 == static_cast<int>(mode); });
|
||||
return it == offered.end() ? 0 : *it;
|
||||
};
|
||||
resolved = newest(Gr_fsr_mode);
|
||||
if (resolved == 0) {
|
||||
// Upscale is only offered with a larger window
|
||||
resolved = newest(FsrMode::AA);
|
||||
}
|
||||
}
|
||||
|
||||
fsr_apply_setting(resolved);
|
||||
options->setOverride("Graphics.FSR", std::unique_ptr<json_t>(json_integer(resolved)), "-fsr");
|
||||
}
|
||||
|
||||
// coverity[GLOBAL_INIT_ORDER] -- safe; OptionBuilder::finish() uses Meyers singleton
|
||||
static auto FsrOption __UNUSED = options::OptionBuilder<int>("Graphics.FSR",
|
||||
std::pair<const char*, int>{"AMD FSR", -1},
|
||||
std::pair<const char*, int>{"AMD's temporal anti aliasing and upscaling. AA keeps the rendering resolution. Upscale renders the 3D scene at the configured resolution and upscales it to the window/monitor resolution.", -1})
|
||||
.category(std::make_pair("Graphics", 1825))
|
||||
.level(options::ExpertLevel::Advanced)
|
||||
.enumerator(fsr_setting_enumerator)
|
||||
.display(fsr_setting_display)
|
||||
.flags({options::OptionFlags::ForceMultiValueSelection})
|
||||
.default_func([]() { return fsr_setting(Gr_fsr_mode, Gr_fsr_major_version); } )
|
||||
.change_listener([](const int& setting, bool) {
|
||||
fsr_apply_setting(setting);
|
||||
return true;
|
||||
})
|
||||
.importance(77)
|
||||
.finish();
|
||||
|
||||
extern int Cmdline_msaa_enabled;
|
||||
|
||||
static void parse_msaa_func()
|
||||
@@ -2440,8 +2320,6 @@ bool gr_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps, GraphicsAPI
|
||||
return false;
|
||||
}
|
||||
|
||||
fsr_resolve_cmdline_setting();
|
||||
|
||||
gr_set_palette_internal(Gr_current_palette_name, NULL, 0);
|
||||
|
||||
bm_init();
|
||||
|
||||
+10
-32
@@ -68,27 +68,6 @@ enum class AntiAliasMode {
|
||||
extern AntiAliasMode Gr_aa_mode;
|
||||
extern AntiAliasMode Gr_aa_mode_last_frame;
|
||||
|
||||
// AA renders at 1:1. Upscale upscales the scene from the render resolution to the window resolution, and everything
|
||||
// drawn after it then draws at that size.
|
||||
enum class FsrMode {
|
||||
Off = 0,
|
||||
AA = 1,
|
||||
Upscale = 2,
|
||||
};
|
||||
extern FsrMode Gr_fsr_mode;
|
||||
// The chosen FSR major version, or 0 for the newest the renderer offers
|
||||
extern int Gr_fsr_major_version;
|
||||
// The FSR versions the renderer offers, newest first, such as "4.1.2". Empty without FSR.
|
||||
extern SCP_vector<SCP_string> Gr_fsr_versions;
|
||||
// The largest image side the renderer supports, which limits the upscale factor. 0 without FSR.
|
||||
extern int Gr_fsr_max_output_size;
|
||||
|
||||
// Scales the render resolution to cover the window in both axes, keeping its aspect ratio; the final blit squeezes the
|
||||
// rest. 1 for Off and AA, and when the window isn't larger than the render resolution.
|
||||
float gr_fsr_upscale_factor(FsrMode mode);
|
||||
// Framebuffer pixels per gr_screen unit for what's drawn after the scene. Above 1 only while FSR upscales.
|
||||
extern float Gr_composition_scale;
|
||||
|
||||
bool gr_is_fxaa_mode(AntiAliasMode mode);
|
||||
bool gr_is_smaa_mode(AntiAliasMode mode);
|
||||
|
||||
@@ -253,8 +232,6 @@ enum shader_type {
|
||||
|
||||
SDR_TYPE_GAMMA_BLIT,
|
||||
|
||||
SDR_TYPE_MOTION_VECTORS,
|
||||
|
||||
NUM_SHADER_TYPES
|
||||
};
|
||||
|
||||
@@ -285,7 +262,6 @@ 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)
|
||||
|
||||
@@ -325,7 +301,6 @@ struct vertex_format_data
|
||||
RADIUS,
|
||||
UVEC,
|
||||
MATRIX4,
|
||||
MOTION4, // Vulkan only. OpenGL's attribute tables have no slot for it.
|
||||
};
|
||||
|
||||
vertex_format format_type;
|
||||
@@ -395,13 +370,7 @@ enum class gr_capability {
|
||||
CAPABILITY_INSTANCED_RENDERING,
|
||||
CAPABILITY_FAST_SHADOWS,
|
||||
CAPABILITY_QUERIES_REUSABLE,
|
||||
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,
|
||||
CAPABILITY_FSR
|
||||
CAPABILITY_RAYTRACED_SHADOWS
|
||||
};
|
||||
|
||||
struct gr_capability_def {
|
||||
@@ -910,6 +879,13 @@ typedef struct screen {
|
||||
std::function<void(int pm_id)> gf_model_loaded = [](int) {};
|
||||
std::function<void(int pm_id)> gf_model_unloaded = [](int) {};
|
||||
|
||||
// Rebuilds the raytraced-shadow top-level acceleration structure for the
|
||||
// current frame from the live shadow-casting object set. Called once per
|
||||
// frame from shadows_render_all(), alongside the cascaded shadow map pass --
|
||||
// the shading pass picks between the two per shadows_use_raytracing().
|
||||
// No-op default.
|
||||
std::function<void()> gf_build_shadow_tlas = []() {};
|
||||
|
||||
std::function<void(gr_buffer_handle handle, size_t size, const void* data)> gf_update_buffer_data;
|
||||
std::function<void(gr_buffer_handle handle, size_t offset, size_t size, const void* data)>
|
||||
gf_update_buffer_data_offset;
|
||||
@@ -1351,6 +1327,8 @@ inline void gr_model_unloaded(int pm_id)
|
||||
gr_screen.gf_model_unloaded(pm_id);
|
||||
}
|
||||
|
||||
#define gr_build_shadow_tlas GR_CALL(gr_screen.gf_build_shadow_tlas)
|
||||
|
||||
#define gr_update_buffer_data GR_CALL(gr_screen.gf_update_buffer_data)
|
||||
#define gr_update_buffer_data_offset GR_CALL(gr_screen.gf_update_buffer_data_offset)
|
||||
inline void* gr_map_buffer(gr_buffer_handle handle)
|
||||
|
||||
@@ -79,8 +79,6 @@ public:
|
||||
vec4 Clr;
|
||||
float Clr_scale;
|
||||
int Depth_bias;
|
||||
bool Velocity_output = false;
|
||||
bool Texture_lod_bias = true;
|
||||
bvec4 Color_mask;
|
||||
bool Stencil_test = false;
|
||||
uint32_t Stencil_mask = 0xFF;
|
||||
@@ -133,14 +131,6 @@ public:
|
||||
void set_depth_bias(int bias);
|
||||
int get_depth_bias() const;
|
||||
|
||||
// Whether this writes its own motion vectors when the target has a place for them
|
||||
void set_velocity_output(bool output) { Velocity_output = output; }
|
||||
bool get_velocity_output() const { return Velocity_output; }
|
||||
|
||||
// Whether textures take the LOD bias of temporal upscaling, which sharpens what it reconstructs
|
||||
void set_texture_lod_bias(bool bias) { Texture_lod_bias = bias; }
|
||||
bool get_texture_lod_bias() const { return Texture_lod_bias; }
|
||||
|
||||
void set_color(float red, float green, float blue, float alpha);
|
||||
void set_color(int r, int g, int b, int a);
|
||||
void set_color(const color &clr_in);
|
||||
|
||||
@@ -31,98 +31,6 @@ static bool matrix_uniform_up_to_date = false;
|
||||
static bool gr_ortho_override_active = false;
|
||||
static float gr_ortho_override_distance = 0.0f;
|
||||
|
||||
bool Gr_motion_vectors_active = false;
|
||||
|
||||
namespace {
|
||||
struct velocity_camera {
|
||||
matrix4 view_proj;
|
||||
matrix4 previous_view_proj;
|
||||
|
||||
void capture()
|
||||
{
|
||||
previous_view_proj = view_proj;
|
||||
vm_matrix4_x_matrix4(&view_proj, &gr_projection_matrix, &gr_view_matrix);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static velocity_camera Scene_camera;
|
||||
static velocity_camera Cockpit_camera;
|
||||
static const velocity_camera* Current_velocity_camera = &Scene_camera;
|
||||
static float Scene_viewport_rect[4] = {0.0f, 0.0f, 1.0f, 1.0f};
|
||||
static bool Temporal_reset_requested = true;
|
||||
|
||||
void gr_reset_temporal_history()
|
||||
{
|
||||
Temporal_reset_requested = true;
|
||||
}
|
||||
|
||||
bool gr_consume_temporal_reset()
|
||||
{
|
||||
const bool requested = Temporal_reset_requested;
|
||||
Temporal_reset_requested = false;
|
||||
return requested;
|
||||
}
|
||||
|
||||
static matrix4 Cockpit_last_view_proj;
|
||||
static vec3d Cockpit_sway = vmd_zero_vector;
|
||||
static vec3d Cockpit_last_sway = vmd_zero_vector;
|
||||
|
||||
void gr_capture_scene_camera()
|
||||
{
|
||||
Scene_camera.capture();
|
||||
Cockpit_last_view_proj = Cockpit_camera.view_proj;
|
||||
Cockpit_last_sway = Cockpit_sway;
|
||||
|
||||
Scene_viewport_rect[0] = i2fl(gr_screen.offset_x) / i2fl(gr_screen.max_w);
|
||||
Scene_viewport_rect[1] = i2fl(gr_screen.offset_y) / i2fl(gr_screen.max_h);
|
||||
Scene_viewport_rect[2] = i2fl(gr_screen.clip_width) / i2fl(gr_screen.max_w);
|
||||
Scene_viewport_rect[3] = i2fl(gr_screen.clip_height) / i2fl(gr_screen.max_h);
|
||||
}
|
||||
|
||||
void gr_begin_cockpit_camera(const matrix* ship_orient, const matrix* ship_last_orient, const vec3d* sway)
|
||||
{
|
||||
vm_matrix4_x_matrix4(&Cockpit_camera.view_proj, &gr_projection_matrix, &gr_view_matrix);
|
||||
Cockpit_sway = *sway;
|
||||
|
||||
// Everything drawn here hangs off the ship's rotation plus the sway offset, so last frame's position of a point is
|
||||
// its current one moved by the change of that frame.
|
||||
matrix current = *ship_orient;
|
||||
matrix last = *ship_last_orient;
|
||||
matrix4 current_frame, inverse_current, last_frame, frame_change;
|
||||
vm_matrix4_set_transform(¤t_frame, ¤t, &Cockpit_sway);
|
||||
vm_matrix4_set_transform(&last_frame, &last, &Cockpit_last_sway);
|
||||
vm_inverse_matrix4(&inverse_current, ¤t_frame);
|
||||
vm_matrix4_x_matrix4(&frame_change, &last_frame, &inverse_current);
|
||||
vm_matrix4_x_matrix4(&Cockpit_camera.previous_view_proj, &Cockpit_last_view_proj, &frame_change);
|
||||
|
||||
Current_velocity_camera = &Cockpit_camera;
|
||||
}
|
||||
|
||||
void gr_end_cockpit_camera()
|
||||
{
|
||||
Current_velocity_camera = &Scene_camera;
|
||||
}
|
||||
|
||||
void gr_get_velocity_camera(matrix4* view_proj, matrix4* previous_view_proj, float viewport_rect[4])
|
||||
{
|
||||
*view_proj = Current_velocity_camera->view_proj;
|
||||
*previous_view_proj = Current_velocity_camera->previous_view_proj;
|
||||
memcpy(viewport_rect, Scene_viewport_rect, sizeof(Scene_viewport_rect));
|
||||
}
|
||||
|
||||
void gr_get_velocity_uniforms(graphics::velocity_camera_data* out)
|
||||
{
|
||||
// Only shaders writing motion vectors read this, and those only run in a scene that has them
|
||||
if (!Gr_motion_vectors_active) {
|
||||
return;
|
||||
}
|
||||
out->viewProj = Current_velocity_camera->view_proj;
|
||||
out->prevViewProj = Current_velocity_camera->previous_view_proj;
|
||||
out->uvScale[0] = Scene_viewport_rect[2];
|
||||
out->uvScale[1] = Scene_viewport_rect[3];
|
||||
}
|
||||
|
||||
static matrix4 create_view_matrix(const vec3d* pos, const matrix* orient)
|
||||
{
|
||||
vec3d scaled_pos;
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
#include "graphics/2d.h"
|
||||
#include "render/3d.h"
|
||||
|
||||
namespace graphics {
|
||||
struct velocity_camera_data;
|
||||
}
|
||||
|
||||
extern transform_stack gr_model_matrix_stack;
|
||||
extern matrix4 gr_view_matrix;
|
||||
extern matrix4 gr_model_view_matrix;
|
||||
@@ -15,22 +11,6 @@ extern matrix4 gr_last_projection_matrix;
|
||||
extern matrix4 gr_env_texture_matrix;
|
||||
extern float gr_near_plane;
|
||||
|
||||
// True while the current scene writes motion vectors. Only ever set by a renderer that supports them.
|
||||
extern bool Gr_motion_vectors_active;
|
||||
|
||||
// Records the current view and projection as the main scene's camera, once per rendered scene.
|
||||
void gr_capture_scene_camera();
|
||||
// The cockpit has its own near plane, FOV and frame, so draws between these use a separate camera. It's drawn around the
|
||||
// origin at offsets that turn with the ship and move with its sway, so the camera needs both, and the orientation last frame.
|
||||
void gr_begin_cockpit_camera(const matrix* ship_orient, const matrix* ship_last_orient, const vec3d* sway);
|
||||
void gr_end_cockpit_camera();
|
||||
// The camera that draws write motion vectors against, viewport_rect being xy offset and zw size as fractions of the target.
|
||||
void gr_get_velocity_camera(matrix4* view_proj, matrix4* previous_view_proj, float viewport_rect[4]);
|
||||
void gr_get_velocity_uniforms(graphics::velocity_camera_data* out);
|
||||
// The camera moved discontinuously, so temporal effects must not reuse earlier frames.
|
||||
void gr_reset_temporal_history();
|
||||
bool gr_consume_temporal_reset();
|
||||
|
||||
void gr_matrix_on_frame();
|
||||
|
||||
void gr_start_instance_matrix(const vec3d* offset, const matrix* rotation);
|
||||
|
||||
@@ -1631,20 +1631,6 @@ 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
|
||||
case gr_capability::CAPABILITY_FSR:
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -253,9 +253,6 @@ 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(SHADOW_MAP_RAW_TEXTURE_UNIT, GL_TEXTURE_2D_ARRAY, Shadow_map_depth_texture, Shadow_map_raw_sampler);
|
||||
}
|
||||
}
|
||||
|
||||
if (ENVMAP > 0) {
|
||||
@@ -317,8 +314,8 @@ void gr_opengl_deferred_lighting_finish()
|
||||
auto lp = ltp::current();
|
||||
|
||||
auto header = light_uniform_aligner.getHeader<deferred_global_data>();
|
||||
shadows_fill_deferred_global_data(*header);
|
||||
if (Shadow_quality != ShadowQuality::Disabled) {
|
||||
vm_inverse_matrix4(&header->inv_view_matrix, &Shadow_view_matrix_render);
|
||||
shadow_cascade_params_bind_deferred();
|
||||
}
|
||||
|
||||
|
||||
@@ -433,7 +433,6 @@ 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);
|
||||
Current_shader->program->Uniforms.setTextureUniform("shadow_map_raw", SHADOW_MAP_RAW_TEXTURE_UNIT);
|
||||
break;
|
||||
|
||||
case SDR_TYPE_PASSTHROUGH_RENDER:
|
||||
|
||||
@@ -37,7 +37,6 @@ 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);
|
||||
|
||||
@@ -94,20 +93,17 @@ void opengl_texture_state::Enable(GLuint tex_id)
|
||||
return;
|
||||
}
|
||||
|
||||
glBindTexture(units[active_texture_unit].texture_target, tex_id);
|
||||
units[active_texture_unit].texture_id = tex_id;
|
||||
|
||||
// A sampler that an earlier bind set belongs to the earlier texture. No GL call when
|
||||
// the unit has no sampler.
|
||||
BindSampler(active_texture_unit, 0);
|
||||
if (units[active_texture_unit].texture_id != tex_id) {
|
||||
glBindTexture(units[active_texture_unit].texture_target, tex_id);
|
||||
units[active_texture_unit].texture_id = tex_id;
|
||||
}
|
||||
}
|
||||
|
||||
void opengl_texture_state::Enable(GLuint unit, GLenum tex_target, GLuint tex_id, GLuint sampler) {
|
||||
void opengl_texture_state::Enable(GLuint unit, GLenum tex_target, GLuint tex_id) {
|
||||
Assertion(unit < num_texture_units, "Invalid texture unit value!");
|
||||
|
||||
if (units[unit].texture_target == tex_target && units[unit].texture_id == tex_id
|
||||
&& units[unit].bound_sampler == sampler) {
|
||||
// The texture unit already uses this texture and sampler. There is no need to change it
|
||||
if (units[unit].texture_target == tex_target && units[unit].texture_id == tex_id) {
|
||||
// The texture unit already uses this texture. There is no need to change it
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -115,19 +111,6 @@ void opengl_texture_state::Enable(GLuint unit, GLenum tex_target, GLuint tex_id,
|
||||
SetActiveUnit(unit);
|
||||
SetTarget(tex_target);
|
||||
Enable(tex_id);
|
||||
BindSampler(unit, sampler);
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -145,7 +128,6 @@ void opengl_texture_state::Delete(GLuint tex_id)
|
||||
|
||||
glBindTexture(units[i].texture_target, 0);
|
||||
units[i].texture_id = 0;
|
||||
BindSampler(i, 0);
|
||||
|
||||
default_values(i, units[i].texture_target);
|
||||
|
||||
|
||||
@@ -27,12 +27,6 @@ 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,33 +59,13 @@ class opengl_texture_state
|
||||
* @warning This may not actually set the active texture unit to the specified value! If you require that then
|
||||
* you need to call SetActiveUnit!
|
||||
*
|
||||
* The sampler object is part of the unit binding: each call also sets it. So a later bind of
|
||||
* a different texture on this unit removes a sampler that an earlier bind set.
|
||||
*
|
||||
* @param unit The texture unit to use
|
||||
* @param tex_target The texture target of the texture id
|
||||
* @param tex_id The ID of the texture to enable
|
||||
* @param sampler The sampler object for this unit, or 0 for the texture's own sampling state
|
||||
*/
|
||||
void Enable(GLuint unit, GLenum tex_target, GLuint tex_id, GLuint sampler = 0);
|
||||
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
|
||||
*
|
||||
* Prefer the sampler argument of Enable(). Use this function directly only to clear a
|
||||
* sampler before it is deleted.
|
||||
*
|
||||
* 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);
|
||||
};
|
||||
|
||||
@@ -79,7 +79,6 @@ 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;
|
||||
|
||||
@@ -515,24 +514,6 @@ static bool opengl_init_shadow_framebuffer(int size)
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -584,7 +565,24 @@ void opengl_tnl_init()
|
||||
Transform_buffer_handle = opengl_create_texture_buffer_object();
|
||||
|
||||
if (Shadow_quality != ShadowQuality::Disabled) {
|
||||
const int size = shadows_map_resolution();
|
||||
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;
|
||||
}
|
||||
|
||||
if (!opengl_init_shadow_framebuffer(size)) {
|
||||
mprintf(("Failed to create either shadow framebuffer. Disabling shadow support.\n"));
|
||||
@@ -610,15 +608,6 @@ 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(SHADOW_MAP_RAW_TEXTURE_UNIT, 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;
|
||||
@@ -971,14 +960,8 @@ 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);
|
||||
// SHADOW_MAP_RAW_TEXTURE_UNIT 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", SHADOW_MAP_RAW_TEXTURE_UNIT);
|
||||
}
|
||||
Current_shader->program->Uniforms.setTextureUniform("sFramebuffer", 9);
|
||||
if (setAllUniforms || (flags & MODEL_SDR_FLAG_TRANSFORM))
|
||||
Current_shader->program->Uniforms.setTextureUniform("transform_tex", 10);
|
||||
@@ -1061,9 +1044,6 @@ 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(SHADOW_MAP_RAW_TEXTURE_UNIT, GL_TEXTURE_2D_ARRAY, Shadow_map_depth_texture, Shadow_map_raw_sampler);
|
||||
}
|
||||
}
|
||||
|
||||
if (material_info->get_animated_effect() >= 0) {
|
||||
|
||||
@@ -35,15 +35,6 @@ 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;
|
||||
|
||||
// The texture unit of the "shadow_map_raw" sampler, in the model shaders and in the deferred
|
||||
// lighting shader. Unit 7 is the model misc map, so the raw sampler must not use it.
|
||||
constexpr GLuint SHADOW_MAP_RAW_TEXTURE_UNIT = 11;
|
||||
|
||||
struct opengl_vertex_bind {
|
||||
vertex_format_data::vertex_format format;
|
||||
GLint size;
|
||||
|
||||
@@ -39,8 +39,7 @@ void PathRenderer::beginFrame() {
|
||||
Assertion(!m_inFrame,
|
||||
"beginFrame() was called withoug ending previous frame! Check your path renderer calls or get a coder!");
|
||||
|
||||
// The pixel ratio makes text and curves rasterize at the resolution they're drawn at
|
||||
nvgBeginFrame(m_context, gr_screen.max_w, gr_screen.max_h, Gr_composition_scale);
|
||||
nvgBeginFrame(m_context, gr_screen.max_w, gr_screen.max_h, 1.0f);
|
||||
m_inFrame = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,17 +11,13 @@ class model_batch_buffer
|
||||
SCP_vector<matrix4> Submodel_matrices;
|
||||
|
||||
size_t Current_offset = 0;
|
||||
// While motion vectors are active, each batch's matrices are followed by the same number of previous-frame ones.
|
||||
int Prev_stride = 0;
|
||||
|
||||
public:
|
||||
void reset();
|
||||
|
||||
size_t get_buffer_offset() const;
|
||||
int get_prev_stride() const;
|
||||
void set_num_models(int n_models);
|
||||
void set_model_transform(const matrix4 &transform, int model_id);
|
||||
void set_prev_model_transform(const matrix4 &transform, int model_id);
|
||||
|
||||
void submit_buffer_data();
|
||||
|
||||
@@ -34,7 +30,6 @@ protected:
|
||||
SCP_vector<DrawEntryT> _elements;
|
||||
SCP_vector<int> _keys;
|
||||
transform_stack _transforms;
|
||||
transform_stack _prev_transforms; // the same stack, as it stood last frame
|
||||
scene_lights _lights;
|
||||
graphics::util::UniformBuffer _dataBuffer;
|
||||
vec3d _scale;
|
||||
@@ -54,7 +49,6 @@ public:
|
||||
_elements.clear();
|
||||
_keys.clear();
|
||||
_transforms.clear();
|
||||
_prev_transforms.clear();
|
||||
_scale.xyz.x = 1.0f;
|
||||
_scale.xyz.y = 1.0f;
|
||||
_scale.xyz.z = 1.0f;
|
||||
@@ -65,19 +59,11 @@ public:
|
||||
void push_transform(const vec3d* pos, const matrix* orient)
|
||||
{
|
||||
_transforms.push(pos, orient);
|
||||
_prev_transforms.push(pos, orient);
|
||||
}
|
||||
|
||||
void push_transform(const vec3d* pos, const matrix* orient, const vec3d* prev_pos, const matrix* prev_orient)
|
||||
{
|
||||
_transforms.push(pos, orient);
|
||||
_prev_transforms.push(prev_pos, prev_orient);
|
||||
}
|
||||
|
||||
void pop_transform()
|
||||
{
|
||||
_transforms.pop();
|
||||
_prev_transforms.pop();
|
||||
}
|
||||
|
||||
const matrix4& get_transform() const
|
||||
@@ -88,7 +74,6 @@ public:
|
||||
void clear_transforms()
|
||||
{
|
||||
_transforms.clear();
|
||||
_prev_transforms.clear();
|
||||
}
|
||||
|
||||
void set_scale(const vec3d* s)
|
||||
@@ -109,19 +94,15 @@ public:
|
||||
|
||||
void add_submodel_to_batch(int model_num)
|
||||
{
|
||||
const auto batch_matrix = [this](const transform_stack& stack) {
|
||||
matrix4 transform = stack.get_transform();
|
||||
matrix4 transform = _transforms.get_transform();
|
||||
|
||||
vm_vec_scale(&transform.vec.rvec, _scale.xyz.x);
|
||||
vm_vec_scale(&transform.vec.uvec, _scale.xyz.y);
|
||||
vm_vec_scale(&transform.vec.fvec, _scale.xyz.z);
|
||||
vm_vec_scale(&transform.vec.rvec, _scale.xyz.x);
|
||||
vm_vec_scale(&transform.vec.uvec, _scale.xyz.y);
|
||||
vm_vec_scale(&transform.vec.fvec, _scale.xyz.z);
|
||||
|
||||
transform.a1d[15] = 0.0f;
|
||||
return transform;
|
||||
};
|
||||
transform.a1d[15] = 0.0f;
|
||||
|
||||
_batchBuffer.set_model_transform(batch_matrix(_transforms), model_num);
|
||||
_batchBuffer.set_prev_model_transform(batch_matrix(_prev_transforms), model_num);
|
||||
_batchBuffer.set_model_transform(transform, model_num);
|
||||
}
|
||||
|
||||
void init_render(bool sort = true)
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
#include "graphics/rtao.h"
|
||||
|
||||
#include "graphics/2d.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()
|
||||
{
|
||||
// RTAO reads its inputs from the deferred global data, and the frame TLAS build also
|
||||
// runs for RTAO alone. So RTAO does not depend on the shadow settings.
|
||||
return Rtao_samples > 0 && rtao_supported();
|
||||
}
|
||||
|
||||
void rtao_remove_unsupported_options()
|
||||
{
|
||||
if (!rtao_supported()) {
|
||||
// The option has no effect without raytracing support, so remove it.
|
||||
options::OptionsManager::instance()->removeOption(RtaoQualityOption);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#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 and the hardware supports it. The shadow settings have no effect on RTAO.
|
||||
// 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();
|
||||
@@ -116,10 +116,6 @@ static ShaderTypeInfo SHADER_TYPES[] = {
|
||||
|
||||
{ SDR_TYPE_GAMMA_BLIT, "post-v.sdr", "gamma-correct-f.sdr", nullptr,
|
||||
{ VATTRIB_POSITION, VATTRIB_TEXCOORD }, "Gamma correct blit", false },
|
||||
|
||||
// Vulkan-only
|
||||
{ SDR_TYPE_MOTION_VECTORS, "post-v.sdr", "motion-vector-f.sdr", nullptr,
|
||||
{ VATTRIB_POSITION, VATTRIB_TEXCOORD }, "Motion vectors", false },
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
@@ -137,8 +133,6 @@ 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"},
|
||||
@@ -227,7 +221,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 | SDR_FLAG_DEFERRED_RTAO)) != 0;
|
||||
return (flags & SDR_FLAG_DEFERRED_RT_SHADOWS) != 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,6 @@ enum VertexAttributeLocation : uint32_t {
|
||||
VATTRIB_MODEL_MATRIX = 8, // Occupies locations 8-11
|
||||
|
||||
NUM_VERTEX_ATTRIBS = 9,
|
||||
|
||||
// Vulkan only, and not listed in ShaderTypeInfo::attributes, which OpenGL indexes its attribute tables with
|
||||
VATTRIB_EFFECT_MOTION = 12, // vec4
|
||||
};
|
||||
|
||||
// Shared shader type info — matches the original opengl_shader_type_t layout.
|
||||
|
||||
+31
-243
@@ -16,9 +16,7 @@
|
||||
#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"
|
||||
@@ -29,13 +27,9 @@
|
||||
#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;
|
||||
@@ -51,7 +45,6 @@ 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;
|
||||
|
||||
@@ -250,163 +243,13 @@ 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);
|
||||
}
|
||||
|
||||
// Returns the softness of the shadow-mapped sun, relative to the reference above. The tabled
|
||||
// smoothness values are multiplied by this factor.
|
||||
//
|
||||
// The shadow map and the raytracer both use the same value: the tangent of the angular radius
|
||||
// of the sun. This value comes from $SunAngularSize: or from a measurement of the sun bitmap
|
||||
// (see sun_angular_radius_tangent() in starfield.cpp). When the sun becomes two times larger,
|
||||
// the raytraced penumbra cone and this filter width also become two times larger. Thus a
|
||||
// mission author sets one value, and both methods follow it.
|
||||
//
|
||||
// When shadow_contact_hardening_supported() is true, this value is the maximum penumbra radius.
|
||||
// pcssPenumbraRadius() (shadows.sdr) calculates a radius for each pixel from the blocker search,
|
||||
// and clamps it to this maximum (see Shadow_penumbra_scale). When contact hardening is not
|
||||
// supported (OpenGL before 3.3), this value is the fixed penumbra radius. In both cases the
|
||||
// tabled values keep their old meaning, so mods that tuned them see no change.
|
||||
//
|
||||
// This function uses Static_light.front(), which is also the light of the cascades in
|
||||
// shadows_start_render(). Each directional light at this point has a source radius: suns get
|
||||
// it in stars_draw_sun(), and common_setup_room_lights() gives its lights the radius of a sun.
|
||||
// Thus a radius of 0 is a source with no size, for example a blank sun bitmap of a mod. Such a
|
||||
// source must have a hard edge, so the shadow map uses its narrowest filter
|
||||
// (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;
|
||||
}
|
||||
@@ -429,11 +272,6 @@ bool shadows_use_raytracing()
|
||||
return Shadow_render_method == ShadowRenderMethod::Raytraced && shadows_raytracing_supported();
|
||||
}
|
||||
|
||||
bool shadows_rt_scene_needed()
|
||||
{
|
||||
return rtao_enabled() || (shadows_use_raytracing() && Shadow_quality != ShadowQuality::Disabled);
|
||||
}
|
||||
|
||||
bool shadows_use_raytraced_local_lights()
|
||||
{
|
||||
return shadows_use_raytracing() && Rt_shadow_quality == RTShadowQuality::High;
|
||||
@@ -832,9 +670,6 @@ 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)) {
|
||||
@@ -847,18 +682,6 @@ 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);
|
||||
@@ -1014,10 +837,6 @@ void shadows_render_all(fov_t fov, matrix *eye_orient, vec3d *eye_pos,
|
||||
GR_DEBUG_SCOPE("Render shadows");
|
||||
TRACE_SCOPE(tracing::BuildShadowMap);
|
||||
|
||||
// The deferred pass reads this matrix in each frame, also when no shadow map renders (for
|
||||
// example, RTAO in a mission with no sun). Thus write it before the early returns below.
|
||||
Shadow_view_matrix_render = gr_view_matrix;
|
||||
|
||||
if ( Static_light.empty() ) {
|
||||
return;
|
||||
}
|
||||
@@ -1026,8 +845,15 @@ void shadows_render_all(fov_t fov, matrix *eye_orient, vec3d *eye_pos,
|
||||
return;
|
||||
}
|
||||
|
||||
// Rebuilds the raytraced-shadow TLAS alongside the cascaded shadow map below
|
||||
// -- Vulkan-only, no-op everywhere else. Which one shading actually samples
|
||||
// is decided per shadows_use_raytracing(). See VulkanRaytracingManager::buildTlas().
|
||||
gr_build_shadow_tlas();
|
||||
|
||||
//shadows_debug_show_frustum(&Player_obj->orient, &Player_obj->pos, fov, gr_screen.clip_aspect, Min_draw_distance, 3000.0f);
|
||||
|
||||
Shadow_view_matrix_render = gr_view_matrix;
|
||||
|
||||
gr_end_proj_matrix();
|
||||
gr_end_view_matrix();
|
||||
|
||||
@@ -1194,10 +1020,6 @@ 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)
|
||||
@@ -1205,23 +1027,11 @@ 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
|
||||
+ Num_cascade_float_arrays * sizeof(float) * (num_cascades + padding_required),
|
||||
+ sizeof(float) * (num_cascades + padding_required)
|
||||
+ 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;
|
||||
@@ -1233,7 +1043,6 @@ 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() {
|
||||
@@ -1253,7 +1062,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_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(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(cascade_count + cascade_offset <= num_cascades, "Requested drawing out-of-range cascades!");
|
||||
|
||||
SCP_vector<uint8_t> buffer(required_size, 0);
|
||||
@@ -1265,7 +1074,6 @@ 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.shadow_mv_matrix = Shadow_view_matrix_light;
|
||||
|
||||
static_data.shadow_ray_cull_mask = ray.cull_mask;
|
||||
@@ -1281,20 +1089,19 @@ void shadow_cascade_params_bind(int cascade_offset, int cascade_count, const sha
|
||||
offset += sizeof(matrix4);
|
||||
}
|
||||
|
||||
write_padded_floats(buffer, offset, num_cascades, padding,
|
||||
[](int i) { return Shadow_cascade_distances[i]; });
|
||||
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;
|
||||
|
||||
// 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]; });
|
||||
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;
|
||||
|
||||
// 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.
|
||||
@@ -1312,39 +1119,20 @@ shadow_ray_params shadow_ray_params_cockpit(const object* viewer) {
|
||||
return ray;
|
||||
}
|
||||
|
||||
// The ray origin and cull mask of the current deferred lighting pass: the cockpit pass or the
|
||||
// main scene pass.
|
||||
static shadow_ray_params shadow_ray_params_deferred() {
|
||||
if (Lighting_mode == lighting_mode::COCKPIT && Viewer_obj != nullptr) {
|
||||
shadow_ray_params ray = shadow_ray_params_cockpit(Viewer_obj);
|
||||
if (ship_render_player_ship_casts_shadow_on_cockpit()) {
|
||||
ray.cull_mask = TLAS_MASK_ALL;
|
||||
}
|
||||
return ray;
|
||||
}
|
||||
return shadow_ray_params{shadow_rt_relative(Eye_position)};
|
||||
}
|
||||
|
||||
void shadow_cascade_params_bind_deferred() {
|
||||
const bool cockpit = Lighting_mode == lighting_mode::COCKPIT;
|
||||
const int offset = cockpit ? 0 : Num_cockpit_shadow_cascades;
|
||||
const int count = cockpit ? Num_cockpit_shadow_cascades : Num_shadow_cascades;
|
||||
|
||||
shadow_cascade_params_bind(offset, count, shadow_ray_params_deferred());
|
||||
}
|
||||
|
||||
void shadows_fill_deferred_global_data(graphics::deferred_global_data& data) {
|
||||
vm_inverse_matrix4(&data.inv_view_matrix, &Shadow_view_matrix_render);
|
||||
|
||||
const shadow_ray_params ray = shadow_ray_params_deferred();
|
||||
data.rayViewOrigin = ray.view_origin;
|
||||
data.rayCullMask = ray.cull_mask;
|
||||
data.rayBiasMin = Rt_shadow_bias_min;
|
||||
data.rayBiasMax = Rt_shadow_bias_max;
|
||||
|
||||
data.rtaoSampleCount = Rtao_samples;
|
||||
data.rtaoRadius = lighting_profiles::current_rtao_radius();
|
||||
data.rtaoStrength = lighting_profiles::current_rtao_strength();
|
||||
if (cockpit && Viewer_obj != nullptr) {
|
||||
shadow_ray_params ray = shadow_ray_params_cockpit(Viewer_obj);
|
||||
if (ship_render_player_ship_casts_shadow_on_cockpit()) {
|
||||
ray.cull_mask = TLAS_MASK_ALL;
|
||||
}
|
||||
shadow_cascade_params_bind(offset, count, ray);
|
||||
} else {
|
||||
shadow_cascade_params_bind(offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
bool shadows_cockpit_casts_shadow(const ship_info* sip) {
|
||||
|
||||
@@ -17,10 +17,6 @@
|
||||
|
||||
class ship_info;
|
||||
|
||||
namespace graphics {
|
||||
struct deferred_global_data;
|
||||
}
|
||||
|
||||
struct light_frustum_info
|
||||
{
|
||||
matrix4 proj_matrix;
|
||||
@@ -71,32 +67,6 @@ 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.
|
||||
@@ -118,11 +88,6 @@ bool shadows_use_raytracing();
|
||||
// i.e. shadows_use_raytracing() is true AND the user has selected RTShadowQuality::High.
|
||||
bool shadows_use_raytraced_local_lights();
|
||||
|
||||
// Whether this frame needs the ray-tracing TLAS: raytraced shadows with shadows on, or RTAO.
|
||||
// The Vulkan backend builds the TLAS one time in each frame when this is true. See
|
||||
// vulkan_scene_texture_begin().
|
||||
bool shadows_rt_scene_needed();
|
||||
|
||||
extern matrix4 Shadow_view_matrix_light;
|
||||
extern matrix4 Shadow_view_matrix_render;
|
||||
|
||||
@@ -140,38 +105,6 @@ 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,
|
||||
@@ -221,12 +154,6 @@ void shadow_cascade_params_bind(int cascade_offset, int cascade_count, const sha
|
||||
// full-screen passes, which shade one frame per pass.
|
||||
void shadow_cascade_params_bind_deferred();
|
||||
|
||||
// Writes the view and ray-tracing fields of the global data of the deferred lighting pass:
|
||||
// inv_view_matrix, the ray origin and cull mask of the current Lighting_mode, the ray bias,
|
||||
// and the RTAO settings. Both backends call this one time for each deferred lighting pass.
|
||||
// These fields do not depend on the shadow cascades, so RTAO also works with shadows off.
|
||||
void shadows_fill_deferred_global_data(graphics::deferred_global_data& data);
|
||||
|
||||
// True when the viewer's cockpit model also gets a shadow pass (raster and RT).
|
||||
bool shadows_cockpit_casts_shadow(const ship_info* sip);
|
||||
|
||||
|
||||
@@ -35,9 +35,7 @@ void convert_model_material(model_uniform_data* data_out,
|
||||
const model_material& material,
|
||||
const matrix4& model_transform,
|
||||
const vec3d& scale,
|
||||
size_t transform_buffer_offset,
|
||||
const matrix4& prev_model_transform,
|
||||
int prev_transform_stride) {
|
||||
size_t transform_buffer_offset) {
|
||||
auto shader_flags = material.get_shader_flags();
|
||||
|
||||
Assertion(gr_model_matrix_stack.depth() == 1, "Uniform conversion does not respect previous transforms! "
|
||||
@@ -47,10 +45,6 @@ void convert_model_material(model_uniform_data* data_out,
|
||||
scale_matrix(scaled_matrix, scale);
|
||||
|
||||
data_out->modelMatrix = scaled_matrix;
|
||||
data_out->prevModelMatrix = prev_model_transform;
|
||||
scale_matrix(data_out->prevModelMatrix, scale);
|
||||
data_out->prev_transform_stride = prev_transform_stride;
|
||||
gr_get_velocity_uniforms(&data_out->velocity);
|
||||
data_out->viewMatrix = gr_view_matrix;
|
||||
vm_matrix4_x_matrix4(&data_out->modelViewMatrix, &gr_view_matrix, &scaled_matrix);
|
||||
data_out->projMatrix = gr_projection_matrix;
|
||||
|
||||
@@ -16,9 +16,7 @@ void convert_model_material(model_uniform_data* data_out,
|
||||
const model_material& material,
|
||||
const matrix4& model_transform,
|
||||
const vec3d& scale,
|
||||
size_t transform_buffer_offset,
|
||||
const matrix4& prev_model_transform,
|
||||
int prev_transform_stride);
|
||||
size_t transform_buffer_offset);
|
||||
|
||||
void convert_shadow_material(shadow_uniform_data* data_out,
|
||||
const matrix4& model_transform,
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "globalincs/pstypes.h"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
using SPIRV_FLOAT_MAT_4x4 = matrix4;
|
||||
using SPIRV_FLOAT_VEC4 = vec4;
|
||||
|
||||
@@ -19,37 +17,15 @@ namespace graphics {
|
||||
* but the TL;DR here is that everything in here must be 16-byte aligned.
|
||||
*/
|
||||
|
||||
// The camera that draws write motion vectors against. Only the Vulkan shaders declare it.
|
||||
struct velocity_camera_data {
|
||||
matrix4 viewProj;
|
||||
matrix4 prevViewProj;
|
||||
float uvScale[2];
|
||||
};
|
||||
|
||||
struct deferred_global_data {
|
||||
matrix4 inv_view_matrix;
|
||||
|
||||
float invScreenWidth;
|
||||
float invScreenHeight;
|
||||
float nearPlane;
|
||||
// RTAO rays for each pixel. 0 when RTAO is off.
|
||||
int rtaoSampleCount;
|
||||
|
||||
// The ray-tracing inputs of the deferred pass, for raytraced shadows and RTAO. They are
|
||||
// here and not in shadowCascadeParams, because the deferred pass always binds this block,
|
||||
// also when shadows are off. shadows_fill_deferred_global_data() (shadows.cpp) sets them.
|
||||
// rayViewOrigin has the same meaning as shadow_cascade_static_data::shadow_ray_view_origin.
|
||||
// The int after the vec3 fills the std140 padding of the vec3.
|
||||
vec3d rayViewOrigin;
|
||||
int rayCullMask;
|
||||
|
||||
float rayBiasMin;
|
||||
float rayBiasMax;
|
||||
float rtaoRadius;
|
||||
float rtaoStrength;
|
||||
float pad;
|
||||
};
|
||||
static_assert(offsetof(deferred_global_data, rayViewOrigin) == 80 && sizeof(deferred_global_data) == 112,
|
||||
"deferred_global_data must match the std140 layout of the globalDeferredData block in deferred-f.sdr");
|
||||
|
||||
/**
|
||||
* @brief Data for one deferred light rendering call
|
||||
@@ -148,12 +124,6 @@ struct model_uniform_data {
|
||||
float alphaMult;
|
||||
int flags;
|
||||
float pad;
|
||||
|
||||
// Only the Vulkan shaders declare these, and they're past the end of the OpenGL block.
|
||||
matrix4 prevModelMatrix;
|
||||
velocity_camera_data velocity;
|
||||
int prev_transform_stride;
|
||||
float pad2;
|
||||
};
|
||||
|
||||
const size_t model_uniform_data_size = sizeof(model_uniform_data);
|
||||
@@ -176,11 +146,6 @@ struct shadow_cascade_static_data {
|
||||
int cascade_count;
|
||||
float rtShadowBiasMin;
|
||||
float rtShadowBiasMax;
|
||||
int rtShadowSampleCount;
|
||||
// Padding. The scalars must fill 32 bytes, so that std140 puts the following mat4
|
||||
// directly after them. Keep this struct and the shadowCascadeParams block in
|
||||
// shadow_cascade_params.sdr the same.
|
||||
int pad[3];
|
||||
matrix4 shadow_mv_matrix;
|
||||
|
||||
// Position of the view-space origin (the camera) in TLAS-relative space. The RT shadow
|
||||
@@ -208,17 +173,13 @@ 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 shadow_cascade_params.sdr "
|
||||
"if this changes.");
|
||||
// Must match the GLSL shadowCascadeParams block's implicit std140 padding
|
||||
// exactly (32 [5 leading scalars + 3 padding] + 64 [matrix4] + 12 [shadow_ray_view_origin]
|
||||
// + 4 [shadow_ray_cull_mask, packed into the vec3's alignment padding] = 112) --
|
||||
// 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) --
|
||||
// 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) == 112, "shadow_cascade_static_data must match the GLSL shadowCascadeParams layout (see comment above)");
|
||||
static_assert(sizeof(shadow_cascade_static_data) == 96, "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
|
||||
@@ -444,10 +405,6 @@ struct effect_data {
|
||||
int blend_alpha;
|
||||
|
||||
float pad[1];
|
||||
|
||||
// Past the end of the OpenGL block
|
||||
velocity_camera_data velocity;
|
||||
float pad2[2];
|
||||
};
|
||||
|
||||
struct effect_distort_data {
|
||||
@@ -464,10 +421,6 @@ struct batched_data {
|
||||
float intensity;
|
||||
|
||||
float pad[3];
|
||||
|
||||
// Past the end of the OpenGL block
|
||||
velocity_camera_data velocity;
|
||||
float pad2[2];
|
||||
};
|
||||
|
||||
struct post_data {
|
||||
|
||||
@@ -118,12 +118,6 @@ 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
|
||||
*/
|
||||
|
||||
@@ -47,18 +47,16 @@ static void transitionMsaaImages(vk::CommandBuffer cmd, VulkanPostProcessor* pp,
|
||||
vk::AccessFlags2 depthSrcAccess, vk::AccessFlags2 depthDstAccess,
|
||||
vk::PipelineStageFlags2 srcStage, vk::PipelineStageFlags2 dstStage)
|
||||
{
|
||||
std::array<ImageBarrier2, 7> barriers;
|
||||
std::array<ImageBarrier2, 6> barriers;
|
||||
|
||||
std::array<vk::Image, 6> msaaImages = {
|
||||
std::array<vk::Image, 5> msaaImages = {
|
||||
pp->deferred().msaaColorImage(),
|
||||
pp->deferred().msaaPositionImage(),
|
||||
pp->deferred().msaaNormalImage(),
|
||||
pp->deferred().msaaSpecularImage(),
|
||||
pp->deferred().msaaEmissiveImage(),
|
||||
pp->deferred().msaaVelocityImage(),
|
||||
};
|
||||
const size_t colorCount = pp->deferred().velocityActive() ? 6 : 5;
|
||||
for (size_t i = 0; i < colorCount; ++i) {
|
||||
for (size_t i = 0; i < msaaImages.size(); ++i) {
|
||||
barriers[i].image = msaaImages[i];
|
||||
barriers[i].levelCount = 1;
|
||||
barriers[i].layerCount = 1;
|
||||
@@ -70,19 +68,18 @@ static void transitionMsaaImages(vk::CommandBuffer cmd, VulkanPostProcessor* pp,
|
||||
barriers[i].dstAccess = colorDstAccess;
|
||||
}
|
||||
|
||||
auto& depthBarrier = barriers[colorCount];
|
||||
depthBarrier.image = pp->deferred().msaaDepthImage();
|
||||
depthBarrier.aspectMask = imageAspectFromFormat(pp->getDepthFormat());
|
||||
depthBarrier.levelCount = 1;
|
||||
depthBarrier.layerCount = 1;
|
||||
depthBarrier.oldLayout = depthOldLayout;
|
||||
depthBarrier.newLayout = depthNewLayout;
|
||||
depthBarrier.srcStage = srcStage;
|
||||
depthBarrier.srcAccess = depthSrcAccess;
|
||||
depthBarrier.dstStage = dstStage;
|
||||
depthBarrier.dstAccess = depthDstAccess;
|
||||
barriers[5].image = pp->deferred().msaaDepthImage();
|
||||
barriers[5].aspectMask = imageAspectFromFormat(pp->getDepthFormat());
|
||||
barriers[5].levelCount = 1;
|
||||
barriers[5].layerCount = 1;
|
||||
barriers[5].oldLayout = depthOldLayout;
|
||||
barriers[5].newLayout = depthNewLayout;
|
||||
barriers[5].srcStage = srcStage;
|
||||
barriers[5].srcAccess = depthSrcAccess;
|
||||
barriers[5].dstStage = dstStage;
|
||||
barriers[5].dstAccess = depthDstAccess;
|
||||
|
||||
cmdImageBarriers(cmd, ArrayView<const ImageBarrier2>(barriers.data(), colorCount + 1));
|
||||
cmdImageBarriers(cmd, ArrayView<const ImageBarrier2>(barriers.data(), barriers.size()));
|
||||
}
|
||||
|
||||
// ========== Deferred Lighting ==========
|
||||
@@ -173,20 +170,17 @@ void vulkan_deferred_lighting_begin(bool clearNonColorBufs)
|
||||
rpBegin.framebuffer = pp->deferred().msaaFramebuffer();
|
||||
rpBegin.renderArea.offset = vk::Offset2D(0, 0);
|
||||
rpBegin.renderArea.extent = extent;
|
||||
std::array<vk::ClearValue, VulkanDeferredGBuffer::MSAA_COLOR_ATTACHMENT_COUNT + 2> clearValues{};
|
||||
std::array<vk::ClearValue, VulkanDeferredGBuffer::MSAA_COLOR_ATTACHMENT_COUNT + 1> clearValues{};
|
||||
clearValues[VulkanDeferredGBuffer::GBUF_ATT_COLOR].color.setFloat32({0.0f, 0.0f, 0.0f, 0.0f});
|
||||
clearValues[VulkanDeferredGBuffer::GBUF_ATT_POSITION].color.setFloat32({0.0f, 0.0f, 0.0f, 0.0f});
|
||||
clearValues[VulkanDeferredGBuffer::GBUF_ATT_NORMAL].color.setFloat32({0.0f, 0.0f, 0.0f, 0.0f});
|
||||
clearValues[VulkanDeferredGBuffer::GBUF_ATT_SPECULAR].color.setFloat32({0.0f, 0.0f, 0.0f, 0.0f});
|
||||
if (pp->deferred().velocityActive()) {
|
||||
clearValues[VulkanDeferredGBuffer::msaaVelocityAttachmentIndex()].color.setFloat32({VulkanMotionVectors::NO_MOTION_WRITTEN, 0.0f, 0.0f, 0.0f});
|
||||
}
|
||||
clearValues[pp->deferred().msaaDepthAttachmentIndex()].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
clearValues[VulkanDeferredGBuffer::MSAA_COLOR_ATTACHMENT_COUNT].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
rpBegin.clearValueCount = static_cast<uint32_t>(clearValues.size());
|
||||
rpBegin.pClearValues = clearValues.data();
|
||||
cmd.beginRenderPass(rpBegin, vk::SubpassContents::eInline);
|
||||
stateTracker->setRenderPass(pp->deferred().msaaRenderPass(), 0);
|
||||
stateTracker->setColorAttachmentCount(pp->deferred().msaaColorAttachmentCount());
|
||||
stateTracker->setColorAttachmentCount(VulkanDeferredGBuffer::MSAA_COLOR_ATTACHMENT_COUNT);
|
||||
stateTracker->setCurrentSampleCount(renderer->getMsaaSampleCount());
|
||||
}
|
||||
} else {
|
||||
@@ -236,15 +230,15 @@ void vulkan_deferred_lighting_begin(bool clearNonColorBufs)
|
||||
rpBegin.framebuffer = pp->deferred().framebuffer();
|
||||
rpBegin.renderArea.offset = vk::Offset2D(0, 0);
|
||||
rpBegin.renderArea.extent = extent;
|
||||
std::array<vk::ClearValue, VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT + 2> clearValues{};
|
||||
clearValues[pp->deferred().depthAttachmentIndex()].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
std::array<vk::ClearValue, VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT + 1> clearValues{};
|
||||
clearValues[VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
rpBegin.clearValueCount = static_cast<uint32_t>(clearValues.size());
|
||||
rpBegin.pClearValues = clearValues.data();
|
||||
cmd.beginRenderPass(rpBegin, vk::SubpassContents::eInline);
|
||||
stateTracker->setRenderPass(pp->deferred().renderPassLoad(), 0);
|
||||
// The G-buffer pass has multiple color attachments; pipelines built while
|
||||
// it is bound must match, or deferred-on cockpits render incorrectly.
|
||||
stateTracker->setColorAttachmentCount(pp->deferred().colorAttachmentCount());
|
||||
stateTracker->setColorAttachmentCount(VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT);
|
||||
}
|
||||
|
||||
// Optionally clear non-color G-buffer attachments
|
||||
@@ -321,9 +315,9 @@ void vulkan_deferred_lighting_msaa()
|
||||
rpBegin.framebuffer = pp->deferred().msaaResolveFramebuffer();
|
||||
rpBegin.renderArea.offset = vk::Offset2D(0, 0);
|
||||
rpBegin.renderArea.extent = extent;
|
||||
// 5 color, optionally motion vectors, then depth. Color and depth are fully overwritten.
|
||||
std::array<vk::ClearValue, VulkanDeferredGBuffer::MSAA_COLOR_ATTACHMENT_COUNT + 2> clearValues{};
|
||||
clearValues[pp->deferred().msaaDepthAttachmentIndex()].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
// 6 attachments: 5 color + depth. loadOp=eDontCare for all (fully overwritten).
|
||||
std::array<vk::ClearValue, VulkanDeferredGBuffer::MSAA_COLOR_ATTACHMENT_COUNT + 1> clearValues{};
|
||||
clearValues[VulkanDeferredGBuffer::MSAA_COLOR_ATTACHMENT_COUNT].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
rpBegin.clearValueCount = static_cast<uint32_t>(clearValues.size());
|
||||
rpBegin.pClearValues = clearValues.data();
|
||||
cmd.beginRenderPass(rpBegin, vk::SubpassContents::eInline);
|
||||
@@ -354,8 +348,7 @@ void vulkan_deferred_lighting_msaa()
|
||||
config.cullEnabled = false;
|
||||
config.depthWriteEnabled = true;
|
||||
config.renderPass = pp->deferred().msaaResolveRenderPass();
|
||||
config.colorAttachmentCount = pp->deferred().msaaColorAttachmentCount();
|
||||
config.writesVelocity = pp->deferred().velocityActive();
|
||||
config.colorAttachmentCount = 5;
|
||||
|
||||
vertex_layout emptyLayout;
|
||||
vk::Pipeline pipeline = pipelineMgr->getPipeline(config, emptyLayout);
|
||||
@@ -384,7 +377,7 @@ void vulkan_deferred_lighting_msaa()
|
||||
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, [6]=motion vectors if active
|
||||
// [0]=color, [1]=position, [2]=normal, [3]=specular, [4]=emissive, [5]=depth
|
||||
vk::DescriptorSet materialSet = descriptorMgr->allocateFrameSet(DescriptorSetIndex::Material);
|
||||
Assert(materialSet);
|
||||
writer.writeSet(DescriptorSetIndex::Material, materialSet);
|
||||
@@ -404,9 +397,6 @@ void vulkan_deferred_lighting_msaa()
|
||||
texImages[3] = {nearestSampler, pp->deferred().msaaSpecularView(), vk::ImageLayout::eShaderReadOnlyOptimal};
|
||||
texImages[4] = {nearestSampler, pp->deferred().msaaEmissiveView(), vk::ImageLayout::eShaderReadOnlyOptimal};
|
||||
texImages[5] = {nearestSampler, pp->deferred().msaaDepthView(), vk::ImageLayout::eShaderReadOnlyOptimal};
|
||||
if (pp->deferred().velocityActive()) {
|
||||
texImages[6] = {nearestSampler, pp->deferred().msaaVelocityView(), vk::ImageLayout::eShaderReadOnlyOptimal};
|
||||
}
|
||||
writer.setImageArray(MaterialBinding::TextureArray, texImages);
|
||||
|
||||
// PerDraw set: GenericData UBO with {samples, fov} at binding 0
|
||||
@@ -504,13 +494,13 @@ void vulkan_deferred_lighting_msaa()
|
||||
rpBegin.framebuffer = pp->deferred().framebuffer();
|
||||
rpBegin.renderArea.offset = vk::Offset2D(0, 0);
|
||||
rpBegin.renderArea.extent = extent;
|
||||
std::array<vk::ClearValue, VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT + 2> clearValues{};
|
||||
clearValues[pp->deferred().depthAttachmentIndex()].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
std::array<vk::ClearValue, 7> clearValues{};
|
||||
clearValues[6].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
rpBegin.clearValueCount = static_cast<uint32_t>(clearValues.size());
|
||||
rpBegin.pClearValues = clearValues.data();
|
||||
cmd.beginRenderPass(rpBegin, vk::SubpassContents::eInline);
|
||||
stateTracker->setRenderPass(pp->deferred().renderPassLoad(), 0);
|
||||
stateTracker->setColorAttachmentCount(pp->deferred().colorAttachmentCount());
|
||||
stateTracker->setColorAttachmentCount(VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,7 +596,7 @@ void vulkan_deferred_lighting_finish()
|
||||
// only write to fragOut0 — using the G-buffer pass would leave undefined
|
||||
// values at attachment locations 1-5.
|
||||
renderer->setUseGbufRenderPass(false);
|
||||
stateTracker->setColorAttachmentCount(pp->sceneColorAttachmentCount());
|
||||
stateTracker->setColorAttachmentCount(1);
|
||||
|
||||
// Resume scene render pass (loadOp=eLoad) with depth preserved
|
||||
{
|
||||
@@ -616,7 +606,9 @@ void vulkan_deferred_lighting_finish()
|
||||
rpBegin.framebuffer = pp->getSceneFramebuffer();
|
||||
rpBegin.renderArea.offset = vk::Offset2D(0, 0);
|
||||
rpBegin.renderArea.extent = extent;
|
||||
const auto clearValues = pp->sceneClearValues();
|
||||
std::array<vk::ClearValue, 2> clearValues;
|
||||
clearValues[0].color.setFloat32({0.0f, 0.0f, 0.0f, 1.0f});
|
||||
clearValues[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
rpBegin.clearValueCount = static_cast<uint32_t>(clearValues.size());
|
||||
rpBegin.pClearValues = clearValues.data();
|
||||
cmd.beginRenderPass(rpBegin, vk::SubpassContents::eInline);
|
||||
@@ -691,7 +683,10 @@ void vulkan_shadow_map_start(matrix4* shadow_view_matrix, const matrix* light_ma
|
||||
}
|
||||
}
|
||||
|
||||
// End the active render pass, if the state tracker has one.
|
||||
// vulkan_build_shadow_tlas() (called just before this, every frame shadows
|
||||
// are enabled) already ends whatever render pass was active -- TLAS builds
|
||||
// are illegal inside a render pass instance -- and nulls out the tracked
|
||||
// render pass. Only end it here if that didn't already happen.
|
||||
if (stateTracker->getCurrentRenderPass()) {
|
||||
cmd.endRenderPass();
|
||||
}
|
||||
@@ -783,13 +778,13 @@ void vulkan_shadow_map_end()
|
||||
rpBegin.framebuffer = pp->deferred().msaaFramebuffer();
|
||||
rpBegin.renderArea.offset = vk::Offset2D(0, 0);
|
||||
rpBegin.renderArea.extent = extent;
|
||||
std::array<vk::ClearValue, VulkanDeferredGBuffer::MSAA_COLOR_ATTACHMENT_COUNT + 2> clearValues{};
|
||||
clearValues[pp->deferred().msaaDepthAttachmentIndex()].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
std::array<vk::ClearValue, VulkanDeferredGBuffer::MSAA_COLOR_ATTACHMENT_COUNT + 1> clearValues{};
|
||||
clearValues[VulkanDeferredGBuffer::MSAA_COLOR_ATTACHMENT_COUNT].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
rpBegin.clearValueCount = static_cast<uint32_t>(clearValues.size());
|
||||
rpBegin.pClearValues = clearValues.data();
|
||||
cmd.beginRenderPass(rpBegin, vk::SubpassContents::eInline);
|
||||
stateTracker->setRenderPass(pp->deferred().msaaRenderPassLoad(), 0);
|
||||
stateTracker->setColorAttachmentCount(pp->deferred().msaaColorAttachmentCount());
|
||||
stateTracker->setColorAttachmentCount(VulkanDeferredGBuffer::MSAA_COLOR_ATTACHMENT_COUNT);
|
||||
stateTracker->setCurrentSampleCount(getRendererInstance()->getMsaaSampleCount());
|
||||
} else {
|
||||
// Transition scene color: eShaderReadOnlyOptimal → eColorAttachmentOptimal
|
||||
@@ -823,39 +818,35 @@ void vulkan_shadow_map_end()
|
||||
rpBegin.renderArea.offset = vk::Offset2D(0, 0);
|
||||
rpBegin.renderArea.extent = extent;
|
||||
|
||||
std::array<vk::ClearValue, VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT + 2> clearValues{};
|
||||
clearValues[pp->deferred().depthAttachmentIndex()].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
std::array<vk::ClearValue, 7> clearValues{};
|
||||
clearValues[6].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
rpBegin.clearValueCount = static_cast<uint32_t>(clearValues.size());
|
||||
rpBegin.pClearValues = clearValues.data();
|
||||
|
||||
cmd.beginRenderPass(rpBegin, vk::SubpassContents::eInline);
|
||||
stateTracker->setRenderPass(pp->deferred().renderPassLoad(), 0);
|
||||
stateTracker->setColorAttachmentCount(pp->deferred().colorAttachmentCount());
|
||||
stateTracker->setColorAttachmentCount(VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT);
|
||||
}
|
||||
} else {
|
||||
renderer->resumeSceneRendering();
|
||||
}
|
||||
|
||||
vulkan_restore_scene_viewport();
|
||||
}
|
||||
// Restore viewport and scissor to scene size
|
||||
{
|
||||
vk::Viewport viewport;
|
||||
viewport.x = static_cast<float>(gr_screen.offset_x);
|
||||
viewport.y = static_cast<float>(gr_screen.offset_y);
|
||||
viewport.width = static_cast<float>(gr_screen.clip_width);
|
||||
viewport.height = static_cast<float>(gr_screen.clip_height);
|
||||
viewport.minDepth = 0.0f;
|
||||
viewport.maxDepth = 1.0f;
|
||||
cmd.setViewport(0, viewport);
|
||||
|
||||
void vulkan_restore_scene_viewport()
|
||||
{
|
||||
vk::CommandBuffer cmd = getStateTracker()->getCommandBuffer();
|
||||
|
||||
vk::Viewport viewport;
|
||||
viewport.x = static_cast<float>(gr_screen.offset_x);
|
||||
viewport.y = static_cast<float>(gr_screen.offset_y);
|
||||
viewport.width = static_cast<float>(gr_screen.clip_width);
|
||||
viewport.height = static_cast<float>(gr_screen.clip_height);
|
||||
viewport.minDepth = 0.0f;
|
||||
viewport.maxDepth = 1.0f;
|
||||
cmd.setViewport(0, viewport);
|
||||
|
||||
vk::Rect2D scissor;
|
||||
scissor.offset = vk::Offset2D(gr_screen.offset_x, gr_screen.offset_y);
|
||||
scissor.extent = vk::Extent2D(static_cast<uint32_t>(gr_screen.clip_width), static_cast<uint32_t>(gr_screen.clip_height));
|
||||
cmd.setScissor(0, scissor);
|
||||
vk::Rect2D scissor;
|
||||
scissor.offset = vk::Offset2D(gr_screen.offset_x, gr_screen.offset_y);
|
||||
scissor.extent = vk::Extent2D(static_cast<uint32_t>(gr_screen.clip_width), static_cast<uint32_t>(gr_screen.clip_height));
|
||||
cmd.setScissor(0, scissor);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Decal Pass ==========
|
||||
@@ -912,14 +903,14 @@ void vulkan_start_decal_pass()
|
||||
rpBegin.renderArea.offset = vk::Offset2D(0, 0);
|
||||
rpBegin.renderArea.extent = extent;
|
||||
|
||||
std::array<vk::ClearValue, VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT + 2> clearValues{};
|
||||
clearValues[pp->deferred().depthAttachmentIndex()].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
std::array<vk::ClearValue, 7> clearValues{};
|
||||
clearValues[6].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
rpBegin.clearValueCount = static_cast<uint32_t>(clearValues.size());
|
||||
rpBegin.pClearValues = clearValues.data();
|
||||
|
||||
cmd.beginRenderPass(rpBegin, vk::SubpassContents::eInline);
|
||||
stateTracker->setRenderPass(pp->deferred().renderPassLoad(), 0);
|
||||
stateTracker->setColorAttachmentCount(pp->deferred().colorAttachmentCount());
|
||||
stateTracker->setColorAttachmentCount(VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT);
|
||||
}
|
||||
|
||||
// Restore viewport (Y-flipped for Vulkan scene rendering)
|
||||
|
||||
@@ -24,10 +24,6 @@ void vulkan_override_fog(bool set_override);
|
||||
void vulkan_shadow_map_start(matrix4* shadow_view_matrix, const matrix* light_matrix, vec3d* eye_pos, bool first_pass);
|
||||
void vulkan_shadow_map_end();
|
||||
|
||||
// Sets the viewport and scissor to the current gr_screen clip area. Call this after a render
|
||||
// pass resumes in the middle of a scene, because the resume sets a full-extent viewport.
|
||||
void vulkan_restore_scene_viewport();
|
||||
|
||||
// Decal pass
|
||||
void vulkan_start_decal_pass();
|
||||
void vulkan_stop_decal_pass();
|
||||
|
||||
@@ -15,7 +15,6 @@ 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};
|
||||
|
||||
@@ -78,7 +78,6 @@ namespace GlobalBinding {
|
||||
static constexpr uint32_t IrradianceMap = 4; // samplerCube: irradiance map
|
||||
static constexpr uint32_t Tlas = 5; // accelerationStructureEXT: raytraced shadow TLAS (only present when raytraced shadows are supported)
|
||||
static constexpr uint32_t ShadowCascadeParams = 6; // UBO: shadow cascade projection matrices/distances (per-frame, shared by all consumers)
|
||||
static constexpr uint32_t ShadowMapRaw = 7; // sampler2DArray: raw (non-compare) shadow map read, for PCSS blocker search
|
||||
}
|
||||
|
||||
// Material Set (Set 1) bindings — per-material data
|
||||
|
||||
@@ -236,7 +236,7 @@ void VulkanDrawManager::clear()
|
||||
// gr_screen dimensions can exceed the active render pass when rendering to a
|
||||
// smaller off-screen target; vkCmdClearAttachments requires rects inside the
|
||||
// render area.
|
||||
clearRect.rect = stateTracker->clampToRenderArea(stateTracker->screenToFramebuffer(clearRect.rect));
|
||||
clearRect.rect = stateTracker->clampToRenderArea(clearRect.rect);
|
||||
if (clearRect.rect.extent.width == 0 || clearRect.rect.extent.height == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -422,7 +422,7 @@ void VulkanDrawManager::zbufferClear(int mode)
|
||||
clearRect.rect.offset = vk::Offset2D(0, 0);
|
||||
clearRect.rect.extent = vk::Extent2D(static_cast<uint32_t>(gr_screen.max_w),
|
||||
static_cast<uint32_t>(gr_screen.max_h));
|
||||
clearRect.rect = stateTracker->clampToRenderArea(stateTracker->screenToFramebuffer(clearRect.rect));
|
||||
clearRect.rect = stateTracker->clampToRenderArea(clearRect.rect);
|
||||
clearRect.baseArrayLayer = 0;
|
||||
clearRect.layerCount = 1;
|
||||
|
||||
@@ -1116,10 +1116,6 @@ PipelineConfig VulkanDrawManager::buildPipelineConfig(material* mat, primitive_t
|
||||
config.renderPass = stateTracker->getCurrentRenderPass();
|
||||
config.colorAttachmentCount = stateTracker->getColorAttachmentCount();
|
||||
config.sampleCount = stateTracker->getCurrentSampleCount();
|
||||
config.writesVelocity = mat->get_velocity_output()
|
||||
&& (config.shaderType == SDR_TYPE_MODEL || config.shaderType == SDR_TYPE_BATCHED_BITMAP
|
||||
|| config.shaderType == SDR_TYPE_EFFECT_PARTICLE)
|
||||
&& config.colorWriteMask.x;
|
||||
|
||||
return config;
|
||||
}
|
||||
@@ -1131,8 +1127,7 @@ bool VulkanDrawManager::bindMaterialTextures(material* mat, DescriptorWriter* wr
|
||||
// Get sampler matching current texture addressing mode and fallback texture
|
||||
vk::SamplerAddressMode addressMode = convertTextureAddressing(m_textureAddressing);
|
||||
vk::Sampler sampler = texManager->getSampler(
|
||||
vk::Filter::eLinear, vk::Filter::eLinear, addressMode, true, 0.0f, true,
|
||||
mat->get_texture_lod_bias() ? getStateTracker()->getMaterialLodBias() : 0.0f);
|
||||
vk::Filter::eLinear, vk::Filter::eLinear, addressMode, true, 0.0f, true);
|
||||
// OpenGL skips applying texture addressing for AABITMAP, INTERFACE, and CUBEMAP
|
||||
// types - they always stay clamped. We need a clamp sampler for those cases.
|
||||
vk::Sampler clampSampler = texManager->getSampler(
|
||||
@@ -1442,7 +1437,6 @@ bool VulkanDrawManager::applyMaterial(material* mat, primitive_type prim_type, v
|
||||
bindPendingUBOs(DescriptorSetIndex::Global);
|
||||
if (shadowReady) {
|
||||
writer.setImage(GlobalBinding::ShadowMap, pp->getShadowTextureInfo());
|
||||
writer.setImage(GlobalBinding::ShadowMapRaw, pp->getShadowRawTextureInfo());
|
||||
}
|
||||
m_cachedGlobalHadShadow = shadowReady;
|
||||
m_globalSetDirty = false;
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "VulkanTexture.h"
|
||||
#include "VulkanRenderer.h"
|
||||
#include "VulkanPostProcessing.h"
|
||||
#include "VulkanDeferred.h"
|
||||
#include "VulkanDescriptorManager.h"
|
||||
#include "VulkanDeletionQueue.h"
|
||||
#include "VulkanMemory.h"
|
||||
@@ -217,30 +216,7 @@ void vulkan_scene_texture_begin()
|
||||
|
||||
// Switch to HDR scene render pass when post-processing is enabled
|
||||
auto* pp = getPostProcessor();
|
||||
const bool useSceneTarget = pp && pp->isInitialized() && Gr_post_processing_enabled && !PostProcessing_override;
|
||||
|
||||
// Build the ray-tracing TLAS for this frame here, one time, before the scene draws start.
|
||||
// The camera and the objects are final at this point. A TLAS build must be outside a render
|
||||
// pass. The scene pass starts below, so the build adds no pass break to that path.
|
||||
if (vulkan_frame_tlas_pending() && !renderer->isSceneRendering()) {
|
||||
renderer->endCurrentRenderPass();
|
||||
vulkan_build_frame_tlas();
|
||||
|
||||
if (!useSceneTarget) {
|
||||
// No scene pass follows, so resume the pass that the build interrupted.
|
||||
auto* texMgr = getTextureManager();
|
||||
const int rtHandle = (renderer->isRenderTargetActive() && texMgr) ? texMgr->getCurrentRenderTarget() : -1;
|
||||
auto* rtSlot = rtHandle >= 0 ? texMgr->getTextureSlot(rtHandle) : nullptr;
|
||||
if (rtSlot != nullptr) {
|
||||
renderer->resumeRenderTargetPass(rtSlot);
|
||||
} else {
|
||||
renderer->resumeSwapChainPass();
|
||||
}
|
||||
vulkan_restore_scene_viewport();
|
||||
}
|
||||
}
|
||||
|
||||
if (useSceneTarget) {
|
||||
if (pp && pp->isInitialized() && Gr_post_processing_enabled && !PostProcessing_override) {
|
||||
renderer->beginSceneRendering();
|
||||
High_dynamic_range = true;
|
||||
} else {
|
||||
@@ -260,7 +236,7 @@ void vulkan_scene_texture_begin()
|
||||
clearRect.rect.offset = vk::Offset2D(0, 0);
|
||||
clearRect.rect.extent = vk::Extent2D(static_cast<uint32_t>(gr_screen.max_w),
|
||||
static_cast<uint32_t>(gr_screen.max_h));
|
||||
clearRect.rect = stateTracker->clampToRenderArea(stateTracker->screenToFramebuffer(clearRect.rect));
|
||||
clearRect.rect = stateTracker->clampToRenderArea(clearRect.rect);
|
||||
clearRect.baseArrayLayer = 0;
|
||||
clearRect.layerCount = 1;
|
||||
|
||||
@@ -425,10 +401,8 @@ void vulkan_render_primitives_particle(particle_material* material_info,
|
||||
sizeof(graphics::generic_data::effect_data));
|
||||
auto* data = buffer.aligner().addTypedElement<graphics::generic_data::effect_data>();
|
||||
|
||||
// The shader divides gl_FragCoord by these, so they're the pass's size, which is larger than gr_screen on the
|
||||
// composition image while FSR upscales
|
||||
data->window_width = static_cast<float>(getStateTracker()->getRenderArea().width);
|
||||
data->window_height = static_cast<float>(getStateTracker()->getRenderArea().height);
|
||||
data->window_width = static_cast<float>(gr_screen.max_w);
|
||||
data->window_height = static_cast<float>(gr_screen.max_h);
|
||||
data->nearZ = Min_draw_distance;
|
||||
data->farZ = Max_draw_distance;
|
||||
data->srgb = High_dynamic_range ? 1 : 0;
|
||||
@@ -437,7 +411,6 @@ void vulkan_render_primitives_particle(particle_material* material_info,
|
||||
// so linear_depth=1 reads .z directly (matches OpenGL behavior).
|
||||
// Otherwise use the NDC conversion path with the hardware depth copy.
|
||||
data->linear_depth = usePosTexture ? 1 : 0;
|
||||
gr_get_velocity_uniforms(&data->velocity);
|
||||
|
||||
buffer.submitData();
|
||||
gr_bind_uniform_buffer(uniform_block_type::GenericData, buffer.getBufferOffset(0),
|
||||
@@ -486,8 +459,8 @@ void vulkan_render_primitives_distortion(distortion_material* material_info,
|
||||
sizeof(graphics::generic_data::effect_distort_data));
|
||||
auto* data = buffer.aligner().addTypedElement<graphics::generic_data::effect_distort_data>();
|
||||
|
||||
data->window_width = static_cast<float>(getStateTracker()->getRenderArea().width);
|
||||
data->window_height = static_cast<float>(getStateTracker()->getRenderArea().height);
|
||||
data->window_width = static_cast<float>(gr_screen.max_w);
|
||||
data->window_height = static_cast<float>(gr_screen.max_h);
|
||||
data->use_offset = material_info->get_thruster_rendering() ? 1.0f : 0.0f;
|
||||
|
||||
buffer.submitData();
|
||||
@@ -573,7 +546,6 @@ void vulkan_render_primitives_batched(batched_bitmap_material* material_info,
|
||||
auto* data = buffer.aligner().addTypedElement<graphics::generic_data::batched_data>();
|
||||
data->intensity = material_info->get_color_scale();
|
||||
data->color = material_info->get_color();
|
||||
gr_get_velocity_uniforms(&data->velocity);
|
||||
buffer.submitData();
|
||||
gr_bind_uniform_buffer(uniform_block_type::GenericData, buffer.getBufferOffset(0),
|
||||
sizeof(graphics::generic_data::batched_data), buffer.bufferHandle());
|
||||
|
||||
@@ -54,7 +54,7 @@ void blit_to_swapchain_image(VulkanRenderer* renderer, vk::Image dst, uint32_t d
|
||||
{
|
||||
const vk::CommandBuffer cmd = renderer->getCurrentCommandBuffer();
|
||||
const vk::Image composition = renderer->getCurrentCompositionImage();
|
||||
const vk::Extent2D srcExtent = renderer->getCompositionExtent();
|
||||
const vk::Extent2D srcExtent = renderer->getRenderExtent();
|
||||
|
||||
if (!cmd || !composition || !dst) {
|
||||
return;
|
||||
|
||||
@@ -52,7 +52,6 @@ bool PipelineConfig::operator==(const PipelineConfig& other) const
|
||||
renderPass == other.renderPass &&
|
||||
subpass == other.subpass &&
|
||||
colorAttachmentCount == other.colorAttachmentCount &&
|
||||
writesVelocity == other.writesVelocity &&
|
||||
sampleCount == other.sampleCount &&
|
||||
perAttachmentBlendEnabled == other.perAttachmentBlendEnabled &&
|
||||
[&]() {
|
||||
@@ -127,7 +126,6 @@ uint64_t PipelineConfig::hash() const
|
||||
hashCombine(h, handleToU64(static_cast<VkRenderPass>(renderPass)));
|
||||
hashCombine(h, subpass);
|
||||
hashCombine(h, colorAttachmentCount);
|
||||
hashCombine(h, writesVelocity ? 1u : 0u);
|
||||
hashCombine(h, static_cast<uint64_t>(sampleCount));
|
||||
hashCombine(h, perAttachmentBlendEnabled ? 1u : 0u);
|
||||
if (perAttachmentBlendEnabled) {
|
||||
@@ -458,22 +456,6 @@ vk::UniquePipeline VulkanPipelineManager::createPipeline(const PipelineConfig& c
|
||||
}
|
||||
}
|
||||
|
||||
if (config.colorAttachmentCount > PipelineConfig::VELOCITY_ATTACHMENT) {
|
||||
auto& velocity = colorBlendAttachments[PipelineConfig::VELOCITY_ATTACHMENT];
|
||||
velocity = vk::PipelineColorBlendAttachmentState{};
|
||||
if (config.writesVelocity) {
|
||||
// The shader's alpha chooses per fragment whether to replace the motion already there.
|
||||
velocity.blendEnable = VK_TRUE;
|
||||
velocity.srcColorBlendFactor = vk::BlendFactor::eSrcAlpha;
|
||||
velocity.dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha;
|
||||
velocity.colorBlendOp = vk::BlendOp::eAdd;
|
||||
velocity.srcAlphaBlendFactor = vk::BlendFactor::eOne;
|
||||
velocity.dstAlphaBlendFactor = vk::BlendFactor::eZero;
|
||||
velocity.alphaBlendOp = vk::BlendOp::eAdd;
|
||||
velocity.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB;
|
||||
}
|
||||
}
|
||||
|
||||
vk::PipelineColorBlendStateCreateInfo colorBlending;
|
||||
colorBlending.logicOpEnable = VK_FALSE;
|
||||
colorBlending.attachmentCount = static_cast<uint32_t>(colorBlendAttachments.size());
|
||||
|
||||
@@ -61,11 +61,6 @@ struct PipelineConfig {
|
||||
// Color attachment count (for multiple render targets)
|
||||
uint32_t colorAttachmentCount = 1;
|
||||
|
||||
// Motion vector output location on passes that have one (colorAttachmentCount above it). Everything else
|
||||
// must leave it alone: a shader that doesn't write a location leaves the attachment undefined.
|
||||
static constexpr uint32_t VELOCITY_ATTACHMENT = 6;
|
||||
bool writesVelocity = false;
|
||||
|
||||
// MSAA sample count (default e1 = no multisampling)
|
||||
vk::SampleCountFlagBits sampleCount = vk::SampleCountFlagBits::e1;
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "graphics/grinternal.h"
|
||||
#include "graphics/shadows.h"
|
||||
#include "graphics/2d.h"
|
||||
#include "graphics/matrix.h"
|
||||
#include "lighting/lighting_profiles.h"
|
||||
#include "lighting/lighting.h"
|
||||
#include "math/vecmat.h"
|
||||
@@ -54,7 +53,6 @@ bool VulkanPostProcessor::init(vk::Device device, vk::PhysicalDevice physDevice,
|
||||
m_ctx.device = device;
|
||||
m_ctx.memoryManager = memMgr;
|
||||
m_ctx.sceneExtent = extent;
|
||||
m_ctx.outputExtent = extent;
|
||||
m_ctx.depthFormat = depthFormat;
|
||||
m_ctx.hdrActive = hdrActive;
|
||||
// In HDR the tonemapped scene must be able to exceed paper white, so use fp16.
|
||||
@@ -350,8 +348,6 @@ bool VulkanPostProcessor::init(vk::Device device, vk::PhysicalDevice physDevice,
|
||||
// Wire the deferred light accumulation + fog subsystems (resources are lazy).
|
||||
m_lighting.init(m_ctx, m_sceneColor, m_deferred, m_shadow);
|
||||
m_fog.init(m_ctx, m_sceneColor, m_sceneDepth, m_sceneDepthCopy, m_deferred);
|
||||
m_motionVectors.init(m_ctx);
|
||||
m_upscaler.init(m_ctx);
|
||||
|
||||
// Initialize MSAA resources if MSAA is enabled and G-buffer is ready
|
||||
if (m_deferred.isInitialized() && Cmdline_msaa_enabled > 0) {
|
||||
@@ -372,8 +368,6 @@ void VulkanPostProcessor::shutdown()
|
||||
if (m_ctx.device) {
|
||||
m_ctx.device.waitIdle();
|
||||
|
||||
m_upscaler.destroyTargets();
|
||||
m_motionVectors.shutdown();
|
||||
m_fog.shutdown();
|
||||
shutdownShadowPass();
|
||||
shadow_cascade_params_shutdown();
|
||||
@@ -397,14 +391,6 @@ void VulkanPostProcessor::shutdown()
|
||||
}
|
||||
destroySceneTargets();
|
||||
|
||||
if (m_velocitySceneRenderPassLoad) {
|
||||
m_ctx.device.destroyRenderPass(m_velocitySceneRenderPassLoad);
|
||||
m_velocitySceneRenderPassLoad = nullptr;
|
||||
}
|
||||
if (m_velocitySceneRenderPass) {
|
||||
m_ctx.device.destroyRenderPass(m_velocitySceneRenderPass);
|
||||
m_velocitySceneRenderPass = nullptr;
|
||||
}
|
||||
if (m_sceneRenderPassLoad) {
|
||||
m_ctx.device.destroyRenderPass(m_sceneRenderPassLoad);
|
||||
m_sceneRenderPassLoad = nullptr;
|
||||
@@ -504,10 +490,6 @@ void VulkanPostProcessor::destroySceneTargets()
|
||||
m_ctx.device.destroyFramebuffer(m_sceneFramebuffer);
|
||||
m_sceneFramebuffer = nullptr;
|
||||
}
|
||||
if (m_velocitySceneFramebuffer) {
|
||||
m_ctx.device.destroyFramebuffer(m_velocitySceneFramebuffer);
|
||||
m_velocitySceneFramebuffer = nullptr;
|
||||
}
|
||||
m_ctx.destroyTarget(m_sceneEffect);
|
||||
m_ctx.destroyTarget(m_sceneColor);
|
||||
m_ctx.destroyTarget(m_sceneDepth);
|
||||
@@ -536,218 +518,17 @@ bool VulkanPostProcessor::createSceneFramebuffer()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanPostProcessor::createVelocitySceneResources()
|
||||
{
|
||||
if (!m_velocitySceneRenderPass) {
|
||||
// Same as the plain scene passes, with motion vectors at the location the model shader writes them.
|
||||
const auto createPass = [this](vk::AttachmentLoadOp loadOp, vk::ImageLayout colorInitial, vk::ImageLayout depthInitial) {
|
||||
std::array<vk::AttachmentDescription, 3> attachments;
|
||||
|
||||
attachments[0].format = HDR_COLOR_FORMAT;
|
||||
attachments[0].loadOp = loadOp;
|
||||
attachments[0].storeOp = vk::AttachmentStoreOp::eStore;
|
||||
attachments[0].stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
|
||||
attachments[0].stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
|
||||
attachments[0].initialLayout = colorInitial;
|
||||
attachments[0].finalLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
|
||||
attachments[1].format = VulkanMotionVectors::DRAWN_FORMAT;
|
||||
attachments[1].loadOp = loadOp;
|
||||
attachments[1].storeOp = vk::AttachmentStoreOp::eStore;
|
||||
attachments[1].stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
|
||||
attachments[1].stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
|
||||
attachments[1].initialLayout = colorInitial;
|
||||
attachments[1].finalLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
|
||||
attachments[2].format = m_ctx.depthFormat;
|
||||
attachments[2].loadOp = loadOp;
|
||||
attachments[2].storeOp = vk::AttachmentStoreOp::eStore;
|
||||
attachments[2].stencilLoadOp = loadOp;
|
||||
attachments[2].stencilStoreOp = vk::AttachmentStoreOp::eStore;
|
||||
attachments[2].initialLayout = depthInitial;
|
||||
attachments[2].finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
|
||||
|
||||
std::array<vk::AttachmentReference, VulkanDeferredGBuffer::VELOCITY_COLOR_LOCATION_COUNT> colorRefs;
|
||||
for (auto& ref : colorRefs) {
|
||||
ref.attachment = VK_ATTACHMENT_UNUSED;
|
||||
ref.layout = vk::ImageLayout::eUndefined;
|
||||
}
|
||||
colorRefs[0].attachment = 0;
|
||||
colorRefs[0].layout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
colorRefs[PipelineConfig::VELOCITY_ATTACHMENT].attachment = 1;
|
||||
colorRefs[PipelineConfig::VELOCITY_ATTACHMENT].layout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
|
||||
vk::AttachmentReference depthRef;
|
||||
depthRef.attachment = 2;
|
||||
depthRef.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
|
||||
|
||||
vk::SubpassDescription subpass;
|
||||
subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
|
||||
subpass.colorAttachmentCount = static_cast<uint32_t>(colorRefs.size());
|
||||
subpass.pColorAttachments = colorRefs.data();
|
||||
subpass.pDepthStencilAttachment = &depthRef;
|
||||
|
||||
vk::SubpassDependency dependency;
|
||||
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependency.dstSubpass = 0;
|
||||
dependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput
|
||||
| vk::PipelineStageFlagBits::eEarlyFragmentTests
|
||||
| vk::PipelineStageFlagBits::eLateFragmentTests
|
||||
| vk::PipelineStageFlagBits::eFragmentShader
|
||||
| vk::PipelineStageFlagBits::eTransfer;
|
||||
dependency.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput
|
||||
| vk::PipelineStageFlagBits::eEarlyFragmentTests;
|
||||
dependency.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite
|
||||
| vk::AccessFlagBits::eDepthStencilAttachmentWrite
|
||||
| vk::AccessFlagBits::eShaderRead
|
||||
| vk::AccessFlagBits::eTransferRead;
|
||||
dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite
|
||||
| vk::AccessFlagBits::eColorAttachmentRead
|
||||
| vk::AccessFlagBits::eDepthStencilAttachmentWrite
|
||||
| vk::AccessFlagBits::eDepthStencilAttachmentRead;
|
||||
|
||||
vk::RenderPassCreateInfo rpInfo;
|
||||
rpInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
|
||||
rpInfo.pAttachments = attachments.data();
|
||||
rpInfo.subpassCount = 1;
|
||||
rpInfo.pSubpasses = &subpass;
|
||||
rpInfo.dependencyCount = 1;
|
||||
rpInfo.pDependencies = &dependency;
|
||||
|
||||
return m_ctx.device.createRenderPass(rpInfo);
|
||||
};
|
||||
|
||||
try {
|
||||
m_velocitySceneRenderPass = createPass(vk::AttachmentLoadOp::eClear,
|
||||
vk::ImageLayout::eUndefined, vk::ImageLayout::eUndefined);
|
||||
m_velocitySceneRenderPassLoad = createPass(vk::AttachmentLoadOp::eLoad,
|
||||
vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eDepthStencilAttachmentOptimal);
|
||||
} catch (const vk::SystemError& e) {
|
||||
nprintf(("vulkan", "VulkanPostProcessor: Failed to create motion vector scene passes: %s\n", e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_velocitySceneFramebuffer) {
|
||||
std::array<vk::ImageView, 3> views = {m_sceneColor.view, m_motionVectors.drawnMotion().view, m_sceneDepth.view};
|
||||
|
||||
vk::FramebufferCreateInfo fbInfo;
|
||||
fbInfo.renderPass = m_velocitySceneRenderPass;
|
||||
fbInfo.attachmentCount = static_cast<uint32_t>(views.size());
|
||||
fbInfo.pAttachments = views.data();
|
||||
fbInfo.width = m_ctx.sceneExtent.width;
|
||||
fbInfo.height = m_ctx.sceneExtent.height;
|
||||
fbInfo.layers = 1;
|
||||
|
||||
try {
|
||||
m_velocitySceneFramebuffer = m_ctx.device.createFramebuffer(fbInfo);
|
||||
} catch (const vk::SystemError& e) {
|
||||
nprintf(("vulkan", "VulkanPostProcessor: Failed to create motion vector scene framebuffer: %s\n", e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanPostProcessor::prepareMotionVectors()
|
||||
{
|
||||
const bool active = VulkanMotionVectors::wanted()
|
||||
&& m_motionVectors.createResources()
|
||||
&& createVelocitySceneResources()
|
||||
&& m_deferred.setVelocityTarget(&m_motionVectors.drawnMotion());
|
||||
|
||||
if (!active) {
|
||||
m_deferred.setVelocityTarget(nullptr);
|
||||
}
|
||||
|
||||
m_velocityActive = active;
|
||||
Gr_motion_vectors_active = active;
|
||||
}
|
||||
|
||||
void VulkanPostProcessor::setPostProcessInput(const RenderTarget& input)
|
||||
{
|
||||
m_bloom.setSceneColor(input);
|
||||
m_ldr.setSceneColor(input);
|
||||
if (m_bloom.isInitialized() && !m_bloom.resize()) {
|
||||
nprintf(("vulkan", "VulkanPostProcessor: Bloom rebuild failed, disabling bloom\n"));
|
||||
m_bloom.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanPostProcessor::prepareUpscaler()
|
||||
{
|
||||
if (m_upscaler.versionChanged()) {
|
||||
m_ctx.device.waitIdle();
|
||||
m_upscaler.destroyTargets();
|
||||
// Bloom and tonemapping must not keep the destroyed output, even if the new version then fails
|
||||
setPostProcessInput(m_sceneColor);
|
||||
m_upscalerActive = false;
|
||||
}
|
||||
|
||||
const bool active = m_velocityActive && VulkanUpscaler::wanted() && m_upscaler.createResources();
|
||||
|
||||
if (active != m_upscalerActive) {
|
||||
m_ctx.device.waitIdle();
|
||||
m_upscalerActive = active;
|
||||
setPostProcessInput(postProcessInput());
|
||||
gr_reset_temporal_history();
|
||||
}
|
||||
|
||||
float imageOffsetX = 0.0f;
|
||||
float imageOffsetY = 0.0f;
|
||||
float lodBias = 0.0f;
|
||||
if (active) {
|
||||
m_upscaler.beginFrame(imageOffsetX, imageOffsetY, lodBias);
|
||||
}
|
||||
getStateTracker()->setUpscalerSceneState(imageOffsetX, imageOffsetY, lodBias);
|
||||
}
|
||||
|
||||
void VulkanPostProcessor::executeUpscaler(vk::CommandBuffer cmd)
|
||||
{
|
||||
if (!m_upscalerActive) {
|
||||
return;
|
||||
}
|
||||
if (!m_upscaler.dispatch(cmd, m_sceneColor, m_motionVectors.depth(), m_motionVectors.motion())) {
|
||||
// The rest of this frame must not read the output FSR never wrote. Bloom hasn't been recorded yet, and
|
||||
// the frames still in flight may use its targets, which rebuilding them around the scene color replaces.
|
||||
m_ctx.device.waitIdle();
|
||||
m_upscalerActive = false;
|
||||
setPostProcessInput(m_sceneColor);
|
||||
}
|
||||
}
|
||||
|
||||
std::array<vk::ClearValue, 3> VulkanPostProcessor::sceneClearValues() const
|
||||
{
|
||||
std::array<vk::ClearValue, 3> clearValues;
|
||||
clearValues[0].color.setFloat32({0.0f, 0.0f, 0.0f, 1.0f});
|
||||
if (m_velocityActive) {
|
||||
clearValues[1].color.setFloat32({VulkanMotionVectors::NO_MOTION_WRITTEN, 0.0f, 0.0f, 0.0f});
|
||||
clearValues[2].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
} else {
|
||||
clearValues[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
}
|
||||
return clearValues;
|
||||
}
|
||||
|
||||
bool VulkanPostProcessor::resize(vk::Extent2D newExtent, vk::Extent2D outputExtent)
|
||||
bool VulkanPostProcessor::resize(vk::Extent2D newExtent)
|
||||
{
|
||||
if (!m_initialized) {
|
||||
return false;
|
||||
}
|
||||
if (newExtent == m_ctx.sceneExtent && outputExtent == m_ctx.outputExtent) {
|
||||
if (newExtent == m_ctx.sceneExtent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Caller has already waited for the device to go idle.
|
||||
// Caller (recreateSwapChain) has already waited for the device to go idle.
|
||||
m_ctx.sceneExtent = newExtent;
|
||||
m_ctx.outputExtent = outputExtent;
|
||||
|
||||
// Recreated at the new size on next use. Bloom is rebuilt around the input below.
|
||||
m_upscaler.destroyTargets();
|
||||
m_upscalerActive = false;
|
||||
m_bloom.setSceneColor(m_sceneColor);
|
||||
m_ldr.setSceneColor(m_sceneColor);
|
||||
|
||||
// Scene targets first: every subsystem framebuffer that wires scene
|
||||
// color/depth needs the recreated views.
|
||||
@@ -789,8 +570,6 @@ bool VulkanPostProcessor::resize(vk::Extent2D newExtent, vk::Extent2D outputExte
|
||||
nprintf(("vulkan", "VulkanPostProcessor: Fog resize failed, disabling fog\n"));
|
||||
m_fog.shutdown();
|
||||
}
|
||||
// Recreated at the new size on next use.
|
||||
m_motionVectors.destroyTargets();
|
||||
// Distortion (fixed 32x32) and the shadow map (sized from Shadow_quality)
|
||||
// are extent-independent.
|
||||
|
||||
@@ -897,8 +676,8 @@ void VulkanPostProcessor::blitToSwapChain(vk::CommandBuffer cmd)
|
||||
// Set viewport (non-flipped for post-processing — textures are already
|
||||
// in the correct Vulkan orientation, no Y-flip needed)
|
||||
stateTracker->setViewport(0.0f, 0.0f,
|
||||
static_cast<float>(m_ctx.outputExtent.width),
|
||||
static_cast<float>(m_ctx.outputExtent.height));
|
||||
static_cast<float>(m_ctx.sceneExtent.width),
|
||||
static_cast<float>(m_ctx.sceneExtent.height));
|
||||
|
||||
stateTracker->applyDynamicState();
|
||||
|
||||
@@ -915,7 +694,7 @@ void VulkanPostProcessor::blitToSwapChain(vk::CommandBuffer cmd)
|
||||
texArrayInfos[0].sampler = m_ctx.linearSampler;
|
||||
texArrayInfos[0].imageView = m_ldr.postEffectsApplied() ? m_ldr.luminanceView()
|
||||
: useLdr ? m_ldr.ldrView()
|
||||
: postProcessInput().view;
|
||||
: m_sceneColor.view;
|
||||
writer.setImageArray(MaterialBinding::TextureArray, texArrayInfos);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,6 @@ struct PostProcessContext {
|
||||
vk::Device device;
|
||||
VulkanMemoryManager* memoryManager = nullptr;
|
||||
vk::Extent2D sceneExtent;
|
||||
// Everything from tonemapping to the final blit. Larger than the scene while FSR upscales.
|
||||
vk::Extent2D outputExtent;
|
||||
vk::Format depthFormat = vk::Format::eUndefined;
|
||||
|
||||
// True when the renderer negotiated an HDR10 swap chain. Drives fp16 LDR
|
||||
@@ -115,9 +113,7 @@ struct PostProcessContext {
|
||||
vk::Framebuffer framebuffer, vk::Extent2D extent,
|
||||
int shaderType,
|
||||
const vk::ImageView* views, uint32_t viewCount,
|
||||
const void* uboData, size_t uboSize,
|
||||
vk::Sampler sampler = nullptr,
|
||||
uint32_t colorAttachmentCount = 1);
|
||||
const void* uboData, size_t uboSize);
|
||||
|
||||
/**
|
||||
* @brief Generate a mip chain for an image (transitions mip 0 first)
|
||||
@@ -208,7 +204,6 @@ public:
|
||||
vk::ImageView depthView() const { return m_depth.view; }
|
||||
vk::Image depthImage() const { return m_depth.image; }
|
||||
vk::Sampler compareSampler() const { return m_compareSampler; }
|
||||
vk::Sampler rawSampler() const { return m_rawSampler; }
|
||||
vk::RenderPass renderPass() const { return m_renderPass; }
|
||||
vk::Framebuffer framebuffer() const { return m_framebuffer; }
|
||||
|
||||
@@ -216,7 +211,6 @@ private:
|
||||
PostProcessContext* m_ctx = nullptr;
|
||||
RenderTarget m_depth; // D32F, 2D array (Num_shadow_cascades + Num_cockpit_shadow_cascades layers)
|
||||
vk::Sampler m_compareSampler; // Depth-compare sampler for hardware PCF (sampler2DArrayShadow)
|
||||
vk::Sampler m_rawSampler; // Non-compare sampler on the same view, for PCSS blocker search
|
||||
vk::RenderPass m_renderPass;
|
||||
vk::Framebuffer m_framebuffer;
|
||||
int m_textureSize = 0;
|
||||
@@ -241,9 +235,6 @@ public:
|
||||
bool init(PostProcessContext& ctx, const RenderTarget& sceneColor);
|
||||
void shutdown();
|
||||
|
||||
// The caller follows up with resize(), which rebuilds the framebuffer around it.
|
||||
void setSceneColor(const RenderTarget& sceneColor) { m_sceneColor = &sceneColor; }
|
||||
|
||||
/**
|
||||
* @brief Recreate the extent-sized targets/framebuffers (render passes kept)
|
||||
*
|
||||
@@ -307,8 +298,6 @@ public:
|
||||
|
||||
static constexpr uint32_t GBUF_COLOR_ATTACHMENT_COUNT = 6; // Full layout (with composite)
|
||||
static constexpr uint32_t MSAA_COLOR_ATTACHMENT_COUNT = 5; // MSAA layout (without composite)
|
||||
// With motion vectors the subpass has this many color locations, in both layouts. MSAA leaves location 5 unused.
|
||||
static constexpr uint32_t VELOCITY_COLOR_LOCATION_COUNT = 7;
|
||||
|
||||
static constexpr vk::Format GBUF_FORMAT_COLOR = vk::Format::eR16G16B16A16Sfloat;
|
||||
static constexpr vk::Format GBUF_FORMAT_POSITION = vk::Format::eR16G16B16A16Sfloat;
|
||||
@@ -341,21 +330,6 @@ public:
|
||||
bool isInitialized() const { return m_gbufInitialized; }
|
||||
bool isMsaaInitialized() const { return m_msaaInitialized; }
|
||||
|
||||
/**
|
||||
* @brief Selects the render pass variants with a motion vector attachment for this frame
|
||||
* @param drawnMotion The single-sample image to write into. Null selects the plain variants.
|
||||
* @return false if the resources couldn't be created, in which case the plain variants stay selected
|
||||
*/
|
||||
bool setVelocityTarget(const RenderTarget* drawnMotion);
|
||||
bool velocityActive() const { return m_velocityActive; }
|
||||
uint32_t colorAttachmentCount() const { return m_velocityActive ? VELOCITY_COLOR_LOCATION_COUNT : GBUF_COLOR_ATTACHMENT_COUNT; }
|
||||
uint32_t msaaColorAttachmentCount() const { return m_velocityActive ? VELOCITY_COLOR_LOCATION_COUNT : MSAA_COLOR_ATTACHMENT_COUNT; }
|
||||
// Attachment indices into the clear values, since the unused MSAA location takes no attachment
|
||||
static uint32_t velocityAttachmentIndex() { return GBUF_COLOR_ATTACHMENT_COUNT; }
|
||||
uint32_t depthAttachmentIndex() const { return GBUF_COLOR_ATTACHMENT_COUNT + (m_velocityActive ? 1 : 0); }
|
||||
static uint32_t msaaVelocityAttachmentIndex() { return MSAA_COLOR_ATTACHMENT_COUNT; }
|
||||
uint32_t msaaDepthAttachmentIndex() const { return MSAA_COLOR_ATTACHMENT_COUNT + (m_velocityActive ? 1 : 0); }
|
||||
|
||||
// Mid-frame layout transitions / copies (all outside a render pass)
|
||||
void transitionForResume(vk::CommandBuffer cmd);
|
||||
void copyNormal(vk::CommandBuffer cmd);
|
||||
@@ -363,9 +337,9 @@ public:
|
||||
void transitionMsaaForResume(vk::CommandBuffer cmd);
|
||||
|
||||
// G-buffer accessors
|
||||
vk::RenderPass renderPass() const { return m_velocityActive ? m_velocityRenderPass : m_gbufRenderPass; }
|
||||
vk::RenderPass renderPassLoad() const { return m_velocityActive ? m_velocityRenderPassLoad : m_gbufRenderPassLoad; }
|
||||
vk::Framebuffer framebuffer() const { return m_velocityActive ? m_velocityFramebuffer : m_gbufFramebuffer; }
|
||||
vk::RenderPass renderPass() const { return m_gbufRenderPass; }
|
||||
vk::RenderPass renderPassLoad() const { return m_gbufRenderPassLoad; }
|
||||
vk::Framebuffer framebuffer() const { return m_gbufFramebuffer; }
|
||||
vk::ImageView positionView() const { return m_gbufPosition.view; }
|
||||
vk::ImageView normalView() const { return m_gbufNormal.view; }
|
||||
vk::ImageView specularView() const { return m_gbufSpecular.view; }
|
||||
@@ -377,13 +351,11 @@ public:
|
||||
vk::Image normalImage() const { return m_gbufNormal.image; }
|
||||
|
||||
// MSAA accessors
|
||||
vk::RenderPass msaaRenderPass() const { return m_velocityActive ? m_velocityMsaaRenderPass : m_msaaGbufRenderPass; }
|
||||
vk::RenderPass msaaRenderPassLoad() const { return m_velocityActive ? m_velocityMsaaRenderPassLoad : m_msaaGbufRenderPassLoad; }
|
||||
vk::Framebuffer msaaFramebuffer() const { return m_velocityActive ? m_velocityMsaaFramebuffer : m_msaaGbufFramebuffer; }
|
||||
vk::RenderPass msaaResolveRenderPass() const { return m_velocityActive ? m_velocityResolveRenderPass : m_msaaResolveRenderPass; }
|
||||
vk::Framebuffer msaaResolveFramebuffer() const { return m_velocityActive ? m_velocityResolveFramebuffer : m_msaaResolveFramebuffer; }
|
||||
vk::ImageView msaaVelocityView() const { return m_msaaVelocity.view; }
|
||||
vk::Image msaaVelocityImage() const { return m_msaaVelocity.image; }
|
||||
vk::RenderPass msaaRenderPass() const { return m_msaaGbufRenderPass; }
|
||||
vk::RenderPass msaaRenderPassLoad() const { return m_msaaGbufRenderPassLoad; }
|
||||
vk::Framebuffer msaaFramebuffer() const { return m_msaaGbufFramebuffer; }
|
||||
vk::RenderPass msaaResolveRenderPass() const { return m_msaaResolveRenderPass; }
|
||||
vk::Framebuffer msaaResolveFramebuffer() const { return m_msaaResolveFramebuffer; }
|
||||
vk::RenderPass msaaEmissiveCopyRenderPass() const { return m_msaaEmissiveCopyRenderPass; }
|
||||
vk::Framebuffer msaaEmissiveCopyFramebuffer() const { return m_msaaEmissiveCopyFramebuffer; }
|
||||
vk::ImageView msaaColorView() const { return m_msaaColor.view; }
|
||||
@@ -410,16 +382,10 @@ private:
|
||||
vk::ImageLayout depthInitialLayout;
|
||||
bool useResolveDependency = false;
|
||||
SCP_unordered_map<uint32_t, vk::AttachmentLoadOp> attachmentLoadOpOverrides = {};
|
||||
bool includeVelocity = false;
|
||||
vk::AttachmentLoadOp velocityLoadOp = vk::AttachmentLoadOp::eClear;
|
||||
};
|
||||
vk::RenderPass createGbufRenderPass(const GbufRenderPassConfig& config);
|
||||
vk::Framebuffer createGbufFramebuffer(vk::RenderPass renderPass, bool includeComposite,
|
||||
bool useMsaaImages, const RenderTarget* velocity = nullptr);
|
||||
bool createVelocityRenderPasses();
|
||||
bool createVelocityTargets(const RenderTarget& drawnMotion);
|
||||
void destroyVelocityTargets();
|
||||
void destroyVelocityRenderPasses();
|
||||
bool useMsaaImages);
|
||||
|
||||
bool createTargets();
|
||||
void destroyTargets();
|
||||
@@ -459,92 +425,6 @@ private:
|
||||
vk::RenderPass m_msaaEmissiveCopyRenderPass; // 1 MS color att (for upsample)
|
||||
vk::Framebuffer m_msaaEmissiveCopyFramebuffer;
|
||||
bool m_msaaInitialized = false;
|
||||
|
||||
// ---- Variants with a motion vector attachment, created on first use ----
|
||||
bool m_velocityActive = false;
|
||||
RenderTarget m_msaaVelocity;
|
||||
vk::RenderPass m_velocityRenderPass;
|
||||
vk::RenderPass m_velocityRenderPassLoad;
|
||||
vk::Framebuffer m_velocityFramebuffer;
|
||||
vk::RenderPass m_velocityMsaaRenderPass;
|
||||
vk::RenderPass m_velocityMsaaRenderPassLoad;
|
||||
vk::Framebuffer m_velocityMsaaFramebuffer;
|
||||
vk::RenderPass m_velocityResolveRenderPass;
|
||||
vk::Framebuffer m_velocityResolveFramebuffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Per-pixel screen motion for FSR
|
||||
*
|
||||
* Holds the total motion of each pixel since last frame, in image UV units, unjittered, pointing at where the
|
||||
* pixel was. Resources are created on first use, because most configurations never need them.
|
||||
*/
|
||||
class VulkanMotionVectors {
|
||||
public:
|
||||
static constexpr vk::Format FORMAT = vk::Format::eR16G16Sfloat;
|
||||
// What geometry draws: its motion in xy, and in z the inverse of its view depth, 1/w of its clip position. The
|
||||
// depth comes along so that it always belongs to whatever drew the motion, on any projection.
|
||||
static constexpr vk::Format DRAWN_FORMAT = vk::Format::eR16G16B16A16Sfloat;
|
||||
static constexpr vk::Format DEPTH_FORMAT = vk::Format::eR32Sfloat;
|
||||
// Well outside any real motion. Tested in motion-vector-f.sdr and msaa-f.sdr.
|
||||
static constexpr float NO_MOTION_WRITTEN = 1000.0f;
|
||||
|
||||
static bool wanted();
|
||||
|
||||
void init(PostProcessContext& ctx);
|
||||
void destroyTargets();
|
||||
void shutdown();
|
||||
|
||||
bool createResources();
|
||||
const RenderTarget& drawnMotion() const { return m_drawnMotion; }
|
||||
|
||||
// Outside a render pass, with the drawn motion attachment still in eColorAttachmentOptimal and sceneDepthCopy
|
||||
// holding this frame's final depth.
|
||||
void execute(vk::CommandBuffer cmd, vk::ImageView sceneDepthCopy);
|
||||
|
||||
const RenderTarget& motion() const { return m_target; }
|
||||
const RenderTarget& depth() const { return m_depth; }
|
||||
|
||||
private:
|
||||
PostProcessContext* m_ctx = nullptr;
|
||||
RenderTarget m_drawnMotion; // what geometry wrote during the scene
|
||||
RenderTarget m_target; // the drawn motion, with camera motion filled in everywhere else
|
||||
RenderTarget m_depth; // the depth that motion belongs to, including the cockpit, on the cockpit's projection
|
||||
vk::RenderPass m_renderPass;
|
||||
vk::Framebuffer m_framebuffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief FSR: temporal reconstruction of the HDR scene, ahead of bloom and tonemapping
|
||||
*
|
||||
* Owns AMD's context and the image FSR writes. Resources are created on first use.
|
||||
*/
|
||||
class VulkanUpscaler {
|
||||
public:
|
||||
void init(PostProcessContext& ctx) { m_ctx = &ctx; }
|
||||
void destroyTargets();
|
||||
|
||||
static bool wanted();
|
||||
// The context was made for a different version than the one now chosen
|
||||
bool versionChanged() const { return m_context && m_contextMajorVersion != Gr_fsr_major_version; }
|
||||
bool createResources();
|
||||
const RenderTarget& output() const { return m_output; }
|
||||
|
||||
// Before the scene's first pass. The image offset to draw this frame with, and the texture LOD bias to go with it.
|
||||
void beginFrame(float& imageOffsetX, float& imageOffsetY, float& lodBias);
|
||||
// Outside a render pass, with all inputs in eShaderReadOnlyOptimal. Leaves the output in eShaderReadOnlyOptimal.
|
||||
// False if FSR failed, in which case the output holds nothing usable.
|
||||
bool dispatch(vk::CommandBuffer cmd, const RenderTarget& color, const RenderTarget& depth, const RenderTarget& motion);
|
||||
|
||||
private:
|
||||
PostProcessContext* m_ctx = nullptr;
|
||||
RenderTarget m_output;
|
||||
void* m_context = nullptr; // ffxContext
|
||||
int m_contextMajorVersion = 0;
|
||||
int m_jitterPhaseCount = 1;
|
||||
int m_jitterIndex = 0;
|
||||
float m_jitterX = 0.0f;
|
||||
float m_jitterY = 0.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -723,8 +603,6 @@ public:
|
||||
const VulkanBloom& bloom);
|
||||
void shutdown();
|
||||
|
||||
void setSceneColor(const RenderTarget& sceneColor) { m_sceneColor = &sceneColor; }
|
||||
|
||||
/**
|
||||
* @brief Recreate the extent-sized targets/framebuffers (render passes kept)
|
||||
*
|
||||
@@ -898,16 +776,14 @@ public:
|
||||
/**
|
||||
* @brief Recreate all extent-sized targets/framebuffers for a new resolution
|
||||
*
|
||||
* Called by VulkanRenderer after a device-idle wait, when the swap chain or
|
||||
* the FSR output size changes. Render passes, samplers, and the scratch UBO ring are extent-
|
||||
* Called from VulkanRenderer::recreateSwapChain() after the device-idle
|
||||
* wait. Render passes, samplers, and the scratch UBO ring are extent-
|
||||
* independent and kept alive, so every cached pipeline stays valid.
|
||||
* Individual subsystem failures are non-fatal (the subsystem is disabled,
|
||||
* matching init()); returns false only if the core scene targets could not
|
||||
* be recreated.
|
||||
*
|
||||
* @param outputExtent Size from tonemapping on, which FSR upscales the scene to
|
||||
*/
|
||||
bool resize(vk::Extent2D newExtent, vk::Extent2D outputExtent);
|
||||
bool resize(vk::Extent2D newExtent);
|
||||
|
||||
/**
|
||||
* @brief Per-frame reset (called from VulkanRenderer::setupFrame after the frame fence wait)
|
||||
@@ -925,7 +801,7 @@ public:
|
||||
* This render pass has RGBA16F color + depth attachments with loadOp=eClear.
|
||||
* Used between scene_texture_begin() and scene_texture_end().
|
||||
*/
|
||||
vk::RenderPass getSceneRenderPass() const { return m_velocityActive ? m_velocitySceneRenderPass : m_sceneRenderPass; }
|
||||
vk::RenderPass getSceneRenderPass() const { return m_sceneRenderPass; }
|
||||
|
||||
/**
|
||||
* @brief Get the HDR scene render pass with loadOp=eLoad
|
||||
@@ -934,12 +810,12 @@ public:
|
||||
* the same framebuffer. Used to resume scene rendering after
|
||||
* copy_effect_texture interrupts the pass.
|
||||
*/
|
||||
vk::RenderPass getSceneRenderPassLoad() const { return m_velocityActive ? m_velocitySceneRenderPassLoad : m_sceneRenderPassLoad; }
|
||||
vk::RenderPass getSceneRenderPassLoad() const { return m_sceneRenderPassLoad; }
|
||||
|
||||
/**
|
||||
* @brief Get the HDR scene framebuffer
|
||||
*/
|
||||
vk::Framebuffer getSceneFramebuffer() const { return m_velocityActive ? m_velocitySceneFramebuffer : m_sceneFramebuffer; }
|
||||
vk::Framebuffer getSceneFramebuffer() const { return m_sceneFramebuffer; }
|
||||
|
||||
/**
|
||||
* @brief Get the scene rendering extent
|
||||
@@ -1049,29 +925,6 @@ public:
|
||||
*/
|
||||
void executeSMAA(vk::CommandBuffer cmd) { m_smaa.execute(cmd); }
|
||||
|
||||
// Selects whether this frame's scene writes motion vectors. Before the scene's first render pass.
|
||||
void prepareMotionVectors();
|
||||
|
||||
void executeMotionVectors(vk::CommandBuffer cmd)
|
||||
{
|
||||
if (m_velocityActive) {
|
||||
copySceneDepth(cmd);
|
||||
m_motionVectors.execute(cmd, m_sceneDepthCopy.view);
|
||||
}
|
||||
}
|
||||
|
||||
// Selects whether FSR runs for this frame's scene. After prepareMotionVectors(), which it depends on.
|
||||
void prepareUpscaler();
|
||||
|
||||
void executeUpscaler(vk::CommandBuffer cmd);
|
||||
|
||||
uint32_t sceneColorAttachmentCount() const
|
||||
{
|
||||
return m_velocityActive ? VulkanDeferredGBuffer::VELOCITY_COLOR_LOCATION_COUNT : 1;
|
||||
}
|
||||
// Attachment count is 2 or 3. The extra clear value is ignored on the plain passes.
|
||||
std::array<vk::ClearValue, 3> sceneClearValues() const;
|
||||
|
||||
/**
|
||||
* @brief Execute post-processing effects (saturation, brightness, etc.)
|
||||
*
|
||||
@@ -1258,16 +1111,6 @@ public:
|
||||
return {m_shadow.compareSampler(), m_shadow.depthView(), vk::ImageLayout::eShaderReadOnlyOptimal};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get a ready-to-use DescriptorImageInfo for raw (uncompared) shadow map reads
|
||||
*
|
||||
* Same image view as getShadowTextureInfo(), but with compare mode off, for the PCSS
|
||||
* blocker search (which needs actual depth values, not a compare result).
|
||||
*/
|
||||
vk::DescriptorImageInfo getShadowRawTextureInfo() const {
|
||||
return {m_shadow.rawSampler(), m_shadow.depthView(), vk::ImageLayout::eShaderReadOnlyOptimal};
|
||||
}
|
||||
|
||||
// ========== Fog / Volumetric Nebula ==========
|
||||
|
||||
/**
|
||||
@@ -1374,17 +1217,6 @@ private:
|
||||
|
||||
// ---- Fog / volumetric nebula (self-contained subsystem) ----
|
||||
VulkanFog m_fog;
|
||||
VulkanMotionVectors m_motionVectors;
|
||||
bool createVelocitySceneResources();
|
||||
bool m_velocityActive = false;
|
||||
vk::RenderPass m_velocitySceneRenderPass;
|
||||
vk::RenderPass m_velocitySceneRenderPassLoad;
|
||||
vk::Framebuffer m_velocitySceneFramebuffer;
|
||||
VulkanUpscaler m_upscaler;
|
||||
bool m_upscalerActive = false;
|
||||
// Bloom and tonemapping read from here, which is FSR's output while FSR runs
|
||||
const RenderTarget& postProcessInput() const { return m_upscalerActive ? m_upscaler.output() : m_sceneColor; }
|
||||
void setPostProcessInput(const RenderTarget& input);
|
||||
|
||||
// ---- Distortion (ping-pong textures, self-contained subsystem) ----
|
||||
VulkanDistortion m_distortion;
|
||||
|
||||
@@ -139,9 +139,8 @@ bool VulkanBloom::init(PostProcessContext& ctx, const RenderTarget& sceneColor)
|
||||
|
||||
bool VulkanBloom::createTargets()
|
||||
{
|
||||
// Sized from the input, which is FSR's output while it runs and the scene otherwise
|
||||
m_width = m_sceneColor->width / 2;
|
||||
m_height = m_sceneColor->height / 2;
|
||||
m_width = m_ctx->sceneExtent.width / 2;
|
||||
m_height = m_ctx->sceneExtent.height / 2;
|
||||
|
||||
const uint32_t mipLevels = MAX_MIP_BLUR_LEVELS;
|
||||
|
||||
@@ -239,8 +238,8 @@ bool VulkanBloom::createTargets()
|
||||
fbInfo.renderPass = m_compositeRenderPass;
|
||||
fbInfo.attachmentCount = 1;
|
||||
fbInfo.pAttachments = &m_sceneColor->view;
|
||||
fbInfo.width = m_sceneColor->width;
|
||||
fbInfo.height = m_sceneColor->height;
|
||||
fbInfo.width = m_ctx->sceneExtent.width;
|
||||
fbInfo.height = m_ctx->sceneExtent.height;
|
||||
fbInfo.layers = 1;
|
||||
|
||||
try {
|
||||
@@ -406,7 +405,7 @@ void VulkanBloom::execute(vk::CommandBuffer cmd)
|
||||
|
||||
m_ctx->drawFullscreenTriangle(cmd, m_compositeRenderPass,
|
||||
m_sceneColorFB,
|
||||
vk::Extent2D(m_sceneColor->width, m_sceneColor->height),
|
||||
m_ctx->sceneExtent,
|
||||
SDR_TYPE_POST_PROCESS_BLOOM_COMP,
|
||||
m_tex[0].fullView, m_ctx->mipmapSampler,
|
||||
&compData, sizeof(compData),
|
||||
|
||||
@@ -220,8 +220,7 @@ void PostProcessContext::drawFullscreenTriangleMulti(vk::CommandBuffer cmd, vk::
|
||||
vk::Framebuffer framebuffer, vk::Extent2D extent,
|
||||
int shaderType,
|
||||
const vk::ImageView* views, uint32_t viewCount,
|
||||
const void* uboData, size_t uboSize,
|
||||
vk::Sampler sampler, uint32_t colorAttachmentCount)
|
||||
const void* uboData, size_t uboSize)
|
||||
{
|
||||
GR_DEBUG_SCOPE("Draw full screen triangle (multi-view)");
|
||||
|
||||
@@ -243,7 +242,6 @@ void PostProcessContext::drawFullscreenTriangleMulti(vk::CommandBuffer cmd, vk::
|
||||
config.cullEnabled = false;
|
||||
config.depthWriteEnabled = false;
|
||||
config.renderPass = renderPass;
|
||||
config.colorAttachmentCount = colorAttachmentCount;
|
||||
|
||||
vertex_layout emptyLayout;
|
||||
vk::Pipeline pipeline = pipelineMgr->getPipeline(config, emptyLayout);
|
||||
@@ -286,7 +284,7 @@ void PostProcessContext::drawFullscreenTriangleMulti(vk::CommandBuffer cmd, vk::
|
||||
std::array<vk::DescriptorImageInfo, VulkanDescriptorManager::MAX_TEXTURE_BINDINGS> texArrayInfos;
|
||||
texArrayInfos.fill(descriptorMgr->getFallbacks().texture2D);
|
||||
for (uint32_t i = 0; i < viewCount; i++) {
|
||||
texArrayInfos[i] = {sampler ? sampler : linearSampler, views[i], vk::ImageLayout::eShaderReadOnlyOptimal};
|
||||
texArrayInfos[i] = {linearSampler, views[i], vk::ImageLayout::eShaderReadOnlyOptimal};
|
||||
}
|
||||
writer.setImageArray(MaterialBinding::TextureArray, texArrayInfos);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,8 @@
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "gr_vulkan.h"
|
||||
#include "VulkanRenderer.h"
|
||||
#include "VulkanBarrier.h"
|
||||
#include "VulkanPipeline.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "lighting/lighting_profiles.h"
|
||||
#include "lighting/lighting.h"
|
||||
#include "nebula/neb.h"
|
||||
@@ -38,12 +35,11 @@ vk::RenderPass VulkanDeferredGBuffer::createGbufRenderPass(const GbufRenderPassC
|
||||
|
||||
const uint32_t colorCount = config.includeComposite
|
||||
? GBUF_COLOR_ATTACHMENT_COUNT : MSAA_COLOR_ATTACHMENT_COUNT;
|
||||
const uint32_t velocityIndex = colorCount;
|
||||
const uint32_t depthIndex = colorCount + (config.includeVelocity ? 1 : 0);
|
||||
const uint32_t totalAttachments = depthIndex + 1;
|
||||
const uint32_t depthIndex = colorCount;
|
||||
const uint32_t totalAttachments = colorCount + 1;
|
||||
|
||||
// Max 6 color + 1 velocity + 1 depth = 8 attachments
|
||||
std::array<vk::AttachmentDescription, 8> attachments;
|
||||
// Max 6 color + 1 depth = 7 attachments
|
||||
std::array<vk::AttachmentDescription, 7> attachments;
|
||||
for (uint32_t i = 0; i < colorCount; ++i) {
|
||||
attachments[i].format = COLOR_FORMATS[i];
|
||||
attachments[i].samples = config.samples;
|
||||
@@ -56,21 +52,6 @@ vk::RenderPass VulkanDeferredGBuffer::createGbufRenderPass(const GbufRenderPassC
|
||||
attachments[i].finalLayout = config.colorFinalLayout;
|
||||
}
|
||||
|
||||
// Motion vectors stay an attachment through the whole scene, which spares every mid-scene copy from having
|
||||
// to transition them around each pass end and resume.
|
||||
if (config.includeVelocity) {
|
||||
auto& velocity = attachments[velocityIndex];
|
||||
velocity.format = VulkanMotionVectors::DRAWN_FORMAT;
|
||||
velocity.samples = config.samples;
|
||||
velocity.loadOp = config.velocityLoadOp;
|
||||
velocity.storeOp = vk::AttachmentStoreOp::eStore;
|
||||
velocity.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
|
||||
velocity.stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
|
||||
velocity.initialLayout = config.velocityLoadOp == vk::AttachmentLoadOp::eLoad
|
||||
? vk::ImageLayout::eColorAttachmentOptimal : vk::ImageLayout::eUndefined;
|
||||
velocity.finalLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
}
|
||||
|
||||
// Depth — stencil ops mirror the depth loadOp
|
||||
attachments[depthIndex].format = m_ctx->depthFormat;
|
||||
attachments[depthIndex].samples = config.samples;
|
||||
@@ -85,19 +66,11 @@ vk::RenderPass VulkanDeferredGBuffer::createGbufRenderPass(const GbufRenderPassC
|
||||
attachments[depthIndex].initialLayout = config.depthInitialLayout;
|
||||
attachments[depthIndex].finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
|
||||
|
||||
std::array<vk::AttachmentReference, VELOCITY_COLOR_LOCATION_COUNT> colorRefs;
|
||||
for (auto& ref : colorRefs) {
|
||||
ref.attachment = VK_ATTACHMENT_UNUSED;
|
||||
ref.layout = vk::ImageLayout::eUndefined;
|
||||
}
|
||||
std::array<vk::AttachmentReference, 6> colorRefs;
|
||||
for (uint32_t i = 0; i < colorCount; ++i) {
|
||||
colorRefs[i].attachment = i;
|
||||
colorRefs[i].layout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
}
|
||||
if (config.includeVelocity) {
|
||||
colorRefs[PipelineConfig::VELOCITY_ATTACHMENT].attachment = velocityIndex;
|
||||
colorRefs[PipelineConfig::VELOCITY_ATTACHMENT].layout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
}
|
||||
|
||||
vk::AttachmentReference depthRef;
|
||||
depthRef.attachment = depthIndex;
|
||||
@@ -105,7 +78,7 @@ vk::RenderPass VulkanDeferredGBuffer::createGbufRenderPass(const GbufRenderPassC
|
||||
|
||||
vk::SubpassDescription subpass;
|
||||
subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
|
||||
subpass.colorAttachmentCount = config.includeVelocity ? VELOCITY_COLOR_LOCATION_COUNT : colorCount;
|
||||
subpass.colorAttachmentCount = colorCount;
|
||||
subpass.pColorAttachments = colorRefs.data();
|
||||
subpass.pDepthStencilAttachment = &depthRef;
|
||||
|
||||
@@ -131,9 +104,7 @@ vk::RenderPass VulkanDeferredGBuffer::createGbufRenderPass(const GbufRenderPassC
|
||||
dependency.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite
|
||||
| vk::AccessFlagBits::eDepthStencilAttachmentWrite
|
||||
| vk::AccessFlagBits::eShaderRead;
|
||||
// The read covers the motion vectors, which the resolve loads and blends
|
||||
dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite
|
||||
| vk::AccessFlagBits::eColorAttachmentRead
|
||||
| vk::AccessFlagBits::eDepthStencilAttachmentWrite;
|
||||
} else {
|
||||
// Standard G-buffer: previous pass may have done transfers (copies), and
|
||||
@@ -150,9 +121,7 @@ vk::RenderPass VulkanDeferredGBuffer::createGbufRenderPass(const GbufRenderPassC
|
||||
| vk::AccessFlagBits::eShaderRead
|
||||
| vk::AccessFlagBits::eTransferRead
|
||||
| vk::AccessFlagBits::eTransferWrite;
|
||||
// The read covers attachments the load variants keep, such as the motion vectors
|
||||
dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite
|
||||
| vk::AccessFlagBits::eColorAttachmentRead
|
||||
| vk::AccessFlagBits::eDepthStencilAttachmentWrite
|
||||
| vk::AccessFlagBits::eDepthStencilAttachmentRead;
|
||||
}
|
||||
@@ -169,10 +138,10 @@ vk::RenderPass VulkanDeferredGBuffer::createGbufRenderPass(const GbufRenderPassC
|
||||
}
|
||||
|
||||
vk::Framebuffer VulkanDeferredGBuffer::createGbufFramebuffer(
|
||||
vk::RenderPass renderPass, bool includeComposite, bool useMsaaImages, const RenderTarget* velocity)
|
||||
vk::RenderPass renderPass, bool includeComposite, bool useMsaaImages)
|
||||
{
|
||||
// Attachment order: color, position, normal, specular, emissive, [composite], [velocity], depth
|
||||
std::array<vk::ImageView, 8> views;
|
||||
// Attachment order: color, position, normal, specular, emissive, [composite], depth
|
||||
std::array<vk::ImageView, 7> views;
|
||||
uint32_t count = 0;
|
||||
|
||||
if (useMsaaImages) {
|
||||
@@ -193,10 +162,6 @@ vk::Framebuffer VulkanDeferredGBuffer::createGbufFramebuffer(
|
||||
views[count++] = m_gbufComposite.view;
|
||||
}
|
||||
|
||||
if (velocity) {
|
||||
views[count++] = velocity->view;
|
||||
}
|
||||
|
||||
views[count++] = useMsaaImages ? m_msaaDepthView : m_sceneDepth->view;
|
||||
|
||||
vk::FramebufferCreateInfo fbInfo;
|
||||
@@ -319,143 +284,8 @@ bool VulkanDeferredGBuffer::createTargets()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanDeferredGBuffer::createVelocityRenderPasses()
|
||||
{
|
||||
if (m_velocityRenderPass) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
m_velocityRenderPass = createGbufRenderPass({
|
||||
true, vk::SampleCountFlagBits::e1,
|
||||
vk::AttachmentLoadOp::eClear, vk::AttachmentLoadOp::eClear,
|
||||
vk::ImageLayout::eUndefined, vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
vk::ImageLayout::eUndefined,
|
||||
false, {}, true, vk::AttachmentLoadOp::eClear,
|
||||
});
|
||||
m_velocityRenderPassLoad = createGbufRenderPass({
|
||||
true, vk::SampleCountFlagBits::e1,
|
||||
vk::AttachmentLoadOp::eLoad, vk::AttachmentLoadOp::eLoad,
|
||||
vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
vk::ImageLayout::eDepthStencilAttachmentOptimal,
|
||||
false, {}, true, vk::AttachmentLoadOp::eLoad,
|
||||
});
|
||||
|
||||
if (m_msaaInitialized) {
|
||||
const auto samples = getRendererInstance()->getMsaaSampleCount();
|
||||
m_velocityMsaaRenderPass = createGbufRenderPass({
|
||||
false, samples,
|
||||
vk::AttachmentLoadOp::eClear, vk::AttachmentLoadOp::eClear,
|
||||
vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eColorAttachmentOptimal,
|
||||
vk::ImageLayout::eDepthStencilAttachmentOptimal,
|
||||
false, {{GBUF_ATT_EMISSIVE, vk::AttachmentLoadOp::eLoad}}, true, vk::AttachmentLoadOp::eClear,
|
||||
});
|
||||
m_velocityMsaaRenderPassLoad = createGbufRenderPass({
|
||||
false, samples,
|
||||
vk::AttachmentLoadOp::eLoad, vk::AttachmentLoadOp::eLoad,
|
||||
vk::ImageLayout::eColorAttachmentOptimal, vk::ImageLayout::eColorAttachmentOptimal,
|
||||
vk::ImageLayout::eDepthStencilAttachmentOptimal,
|
||||
false, {}, true, vk::AttachmentLoadOp::eLoad,
|
||||
});
|
||||
// Loads rather than discards velocity, because the cockpit resolves on top of the main scene's.
|
||||
m_velocityResolveRenderPass = createGbufRenderPass({
|
||||
false, vk::SampleCountFlagBits::e1,
|
||||
vk::AttachmentLoadOp::eDontCare, vk::AttachmentLoadOp::eDontCare,
|
||||
vk::ImageLayout::eUndefined, vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
vk::ImageLayout::eUndefined,
|
||||
true, {}, true, vk::AttachmentLoadOp::eLoad,
|
||||
});
|
||||
}
|
||||
} catch (const vk::SystemError& e) {
|
||||
nprintf(("vulkan", "VulkanDeferredGBuffer: Failed to create motion vector render passes: %s\n", e.what()));
|
||||
destroyVelocityRenderPasses();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanDeferredGBuffer::destroyVelocityRenderPasses()
|
||||
{
|
||||
for (auto* pass : {&m_velocityRenderPass, &m_velocityRenderPassLoad, &m_velocityMsaaRenderPass,
|
||||
&m_velocityMsaaRenderPassLoad, &m_velocityResolveRenderPass}) {
|
||||
if (*pass) {
|
||||
m_ctx->device.destroyRenderPass(*pass);
|
||||
*pass = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VulkanDeferredGBuffer::createVelocityTargets(const RenderTarget& drawnMotion)
|
||||
{
|
||||
if (m_velocityFramebuffer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
m_velocityFramebuffer = createGbufFramebuffer(m_velocityRenderPass, true, false, &drawnMotion);
|
||||
|
||||
if (m_msaaInitialized) {
|
||||
if (!m_ctx->createImage(m_ctx->sceneExtent.width, m_ctx->sceneExtent.height, VulkanMotionVectors::DRAWN_FORMAT,
|
||||
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled,
|
||||
vk::ImageAspectFlagBits::eColor,
|
||||
m_msaaVelocity.image, m_msaaVelocity.view, m_msaaVelocity.allocation,
|
||||
getRendererInstance()->getMsaaSampleCount())) {
|
||||
nprintf(("vulkan", "VulkanDeferredGBuffer: Failed to create MSAA motion vector image!\n"));
|
||||
destroyVelocityTargets();
|
||||
return false;
|
||||
}
|
||||
m_msaaVelocity.format = VulkanMotionVectors::DRAWN_FORMAT;
|
||||
m_msaaVelocity.width = m_ctx->sceneExtent.width;
|
||||
m_msaaVelocity.height = m_ctx->sceneExtent.height;
|
||||
|
||||
m_velocityMsaaFramebuffer = createGbufFramebuffer(m_velocityMsaaRenderPass, false, true, &m_msaaVelocity);
|
||||
m_velocityResolveFramebuffer = createGbufFramebuffer(m_velocityResolveRenderPass, false, false, &drawnMotion);
|
||||
|
||||
getTextureManager()->transitionImageLayout(m_msaaVelocity.image, m_msaaVelocity.format,
|
||||
vk::ImageLayout::eUndefined, vk::ImageLayout::eColorAttachmentOptimal);
|
||||
}
|
||||
} catch (const vk::SystemError& e) {
|
||||
nprintf(("vulkan", "VulkanDeferredGBuffer: Failed to create motion vector framebuffers: %s\n", e.what()));
|
||||
destroyVelocityTargets();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanDeferredGBuffer::destroyVelocityTargets()
|
||||
{
|
||||
for (auto* framebuffer : {&m_velocityFramebuffer, &m_velocityMsaaFramebuffer, &m_velocityResolveFramebuffer}) {
|
||||
if (*framebuffer) {
|
||||
m_ctx->device.destroyFramebuffer(*framebuffer);
|
||||
*framebuffer = nullptr;
|
||||
}
|
||||
}
|
||||
m_ctx->destroyTarget(m_msaaVelocity);
|
||||
}
|
||||
|
||||
bool VulkanDeferredGBuffer::setVelocityTarget(const RenderTarget* drawnMotion)
|
||||
{
|
||||
m_velocityActive = false;
|
||||
|
||||
if (!drawnMotion || !m_gbufInitialized) {
|
||||
return drawnMotion == nullptr;
|
||||
}
|
||||
|
||||
if (!createVelocityRenderPasses() || !createVelocityTargets(*drawnMotion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_velocityActive = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanDeferredGBuffer::destroyTargets()
|
||||
{
|
||||
// Rebuilt at the new size when next selected
|
||||
destroyVelocityTargets();
|
||||
|
||||
if (m_gbufFramebuffer) {
|
||||
m_ctx->device.destroyFramebuffer(m_gbufFramebuffer);
|
||||
m_gbufFramebuffer = nullptr;
|
||||
@@ -501,7 +331,6 @@ void VulkanDeferredGBuffer::shutdown()
|
||||
}
|
||||
|
||||
destroyTargets();
|
||||
destroyVelocityRenderPasses();
|
||||
|
||||
if (m_gbufRenderPassLoad) {
|
||||
m_ctx->device.destroyRenderPass(m_gbufRenderPassLoad);
|
||||
|
||||
@@ -144,7 +144,7 @@ bool VulkanLDR::init(PostProcessContext& ctx, const RenderTarget& sceneColor, co
|
||||
|
||||
m_initialized = true;
|
||||
nprintf(("vulkan", "VulkanLDR: LDR targets initialized (%ux%u, RGBA8)\n",
|
||||
m_ctx->outputExtent.width, m_ctx->outputExtent.height));
|
||||
m_ctx->sceneExtent.width, m_ctx->sceneExtent.height));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ bool VulkanLDR::createTargets()
|
||||
|
||||
// Create Scene_ldr (full resolution) — tonemapped display-referred output.
|
||||
// fp16 in HDR (carries values above paper white), 8-bit UNORM in SDR.
|
||||
if (!m_ctx->createImage(m_ctx->outputExtent.width, m_ctx->outputExtent.height, ldrFormat,
|
||||
if (!m_ctx->createImage(m_ctx->sceneExtent.width, m_ctx->sceneExtent.height, ldrFormat,
|
||||
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled,
|
||||
vk::ImageAspectFlagBits::eColor,
|
||||
m_sceneLdr.image, m_sceneLdr.view, m_sceneLdr.allocation)) {
|
||||
@@ -162,11 +162,11 @@ bool VulkanLDR::createTargets()
|
||||
return false;
|
||||
}
|
||||
m_sceneLdr.format = ldrFormat;
|
||||
m_sceneLdr.width = m_ctx->outputExtent.width;
|
||||
m_sceneLdr.height = m_ctx->outputExtent.height;
|
||||
m_sceneLdr.width = m_ctx->sceneExtent.width;
|
||||
m_sceneLdr.height = m_ctx->sceneExtent.height;
|
||||
|
||||
// Create Scene_luminance (full resolution) — LDR with luma in alpha for FXAA
|
||||
if (!m_ctx->createImage(m_ctx->outputExtent.width, m_ctx->outputExtent.height, ldrFormat,
|
||||
if (!m_ctx->createImage(m_ctx->sceneExtent.width, m_ctx->sceneExtent.height, ldrFormat,
|
||||
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled,
|
||||
vk::ImageAspectFlagBits::eColor,
|
||||
m_sceneLuminance.image, m_sceneLuminance.view, m_sceneLuminance.allocation)) {
|
||||
@@ -174,13 +174,13 @@ bool VulkanLDR::createTargets()
|
||||
return false;
|
||||
}
|
||||
m_sceneLuminance.format = ldrFormat;
|
||||
m_sceneLuminance.width = m_ctx->outputExtent.width;
|
||||
m_sceneLuminance.height = m_ctx->outputExtent.height;
|
||||
m_sceneLuminance.width = m_ctx->sceneExtent.width;
|
||||
m_sceneLuminance.height = m_ctx->sceneExtent.height;
|
||||
|
||||
// Create Scene_ldr_compressed (full resolution) — tonemapped to [0,1] using the
|
||||
// real tone curve, purely for FXAA/SMAA edge detection while HDR output is
|
||||
// active (see executeTonemap/getAADetectionView).
|
||||
if (!m_ctx->createImage(m_ctx->outputExtent.width, m_ctx->outputExtent.height, ldrFormat,
|
||||
if (!m_ctx->createImage(m_ctx->sceneExtent.width, m_ctx->sceneExtent.height, ldrFormat,
|
||||
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled,
|
||||
vk::ImageAspectFlagBits::eColor,
|
||||
m_sceneLdrCompressed.image, m_sceneLdrCompressed.view, m_sceneLdrCompressed.allocation)) {
|
||||
@@ -188,8 +188,8 @@ bool VulkanLDR::createTargets()
|
||||
return false;
|
||||
}
|
||||
m_sceneLdrCompressed.format = ldrFormat;
|
||||
m_sceneLdrCompressed.width = m_ctx->outputExtent.width;
|
||||
m_sceneLdrCompressed.height = m_ctx->outputExtent.height;
|
||||
m_sceneLdrCompressed.width = m_ctx->sceneExtent.width;
|
||||
m_sceneLdrCompressed.height = m_ctx->sceneExtent.height;
|
||||
|
||||
// Create framebuffers
|
||||
{
|
||||
@@ -197,8 +197,8 @@ bool VulkanLDR::createTargets()
|
||||
fbInfo.renderPass = m_ldrRenderPass;
|
||||
fbInfo.attachmentCount = 1;
|
||||
fbInfo.pAttachments = &m_sceneLdr.view;
|
||||
fbInfo.width = m_ctx->outputExtent.width;
|
||||
fbInfo.height = m_ctx->outputExtent.height;
|
||||
fbInfo.width = m_ctx->sceneExtent.width;
|
||||
fbInfo.height = m_ctx->sceneExtent.height;
|
||||
fbInfo.layers = 1;
|
||||
|
||||
try {
|
||||
@@ -313,7 +313,7 @@ void VulkanLDR::executeTonemap(vk::CommandBuffer cmd)
|
||||
|
||||
// HDR scene → Scene_ldr via tonemapping shader
|
||||
m_ctx->drawFullscreenTriangle(cmd, m_ldrRenderPass,
|
||||
m_sceneLdrFB, m_ctx->outputExtent,
|
||||
m_sceneLdrFB, m_ctx->sceneExtent,
|
||||
SDR_TYPE_POST_PROCESS_TONEMAPPING,
|
||||
m_sceneColor->view, m_ctx->linearSampler,
|
||||
&tmData, sizeof(tmData),
|
||||
@@ -328,7 +328,7 @@ void VulkanLDR::executeTonemap(vk::CommandBuffer cmd)
|
||||
// rather than re-running exposure+tonemap from Scene_color a second time.
|
||||
if (m_ctx->hdrActive) {
|
||||
m_ctx->drawFullscreenTriangle(cmd, m_ldrRenderPass,
|
||||
m_sceneLdrCompressedFB, m_ctx->outputExtent,
|
||||
m_sceneLdrCompressedFB, m_ctx->sceneExtent,
|
||||
SDR_TYPE_COPY,
|
||||
m_sceneLdr.view, m_ctx->linearSampler,
|
||||
nullptr, 0,
|
||||
@@ -357,7 +357,7 @@ void VulkanLDR::executeFXAA(vk::CommandBuffer cmd)
|
||||
// alpha computed from the detection proxy, see getAADetectionView)
|
||||
{
|
||||
std::array<vk::ImageView, 2> views = {m_sceneLdr.view, getAADetectionView()};
|
||||
m_ctx->drawFullscreenTriangleMulti(cmd, m_ldrRenderPass, m_sceneLuminanceFB, m_ctx->outputExtent,
|
||||
m_ctx->drawFullscreenTriangleMulti(cmd, m_ldrRenderPass, m_sceneLuminanceFB, m_ctx->sceneExtent,
|
||||
SDR_TYPE_POST_PROCESS_FXAA_PREPASS, views.data(), static_cast<uint32_t>(views.size()),
|
||||
nullptr, 0);
|
||||
}
|
||||
@@ -388,13 +388,13 @@ void VulkanLDR::executeFXAA(vk::CommandBuffer cmd)
|
||||
|
||||
// FXAA main pass: Scene_luminance → Scene_ldr (loadOp=eLoad preserves non-edge pixels)
|
||||
graphics::generic_data::fxaa_data fxaaData;
|
||||
fxaaData.rt_w = static_cast<float>(m_ctx->outputExtent.width);
|
||||
fxaaData.rt_h = static_cast<float>(m_ctx->outputExtent.height);
|
||||
fxaaData.rt_w = static_cast<float>(m_ctx->sceneExtent.width);
|
||||
fxaaData.rt_h = static_cast<float>(m_ctx->sceneExtent.height);
|
||||
fxaaData.pad[0] = 0.0f;
|
||||
fxaaData.pad[1] = 0.0f;
|
||||
|
||||
m_ctx->drawFullscreenTriangle(cmd, m_ldrLoadRenderPass,
|
||||
m_sceneLdrFB, m_ctx->outputExtent,
|
||||
m_sceneLdrFB, m_ctx->sceneExtent,
|
||||
SDR_TYPE_POST_PROCESS_FXAA,
|
||||
m_sceneLuminance.view, m_ctx->linearSampler,
|
||||
&fxaaData, sizeof(fxaaData),
|
||||
@@ -493,7 +493,7 @@ bool VulkanLDR::executePostEffects(vk::CommandBuffer cmd)
|
||||
|
||||
// Post-effects: Scene_ldr → Scene_luminance (reusing luminance target as temp)
|
||||
m_ctx->drawFullscreenTriangle(cmd, m_ldrRenderPass,
|
||||
m_sceneLuminanceFB, m_ctx->outputExtent,
|
||||
m_sceneLuminanceFB, m_ctx->sceneExtent,
|
||||
SDR_TYPE_POST_PROCESS_MAIN,
|
||||
m_sceneLdr.view, m_ctx->linearSampler,
|
||||
&uboData, sizeof(uboData),
|
||||
@@ -593,7 +593,7 @@ void VulkanLDR::executeLightshafts(vk::CommandBuffer cmd)
|
||||
|
||||
// Additive blend lightshafts onto Scene_ldr
|
||||
m_ctx->drawFullscreenTriangle(cmd, m_ldrLoadRenderPass,
|
||||
m_sceneLdrFB, m_ctx->outputExtent,
|
||||
m_sceneLdrFB, m_ctx->sceneExtent,
|
||||
SDR_TYPE_POST_PROCESS_LIGHTSHAFTS,
|
||||
m_sceneDepth->view, m_ctx->linearSampler,
|
||||
&lsData, sizeof(lsData),
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "graphics/grinternal.h"
|
||||
#include "graphics/light.h"
|
||||
#include "graphics/matrix.h"
|
||||
#include "graphics/rtao.h"
|
||||
#include "graphics/shadows.h"
|
||||
#include "graphics/2d.h"
|
||||
#include "bmpman/bmpman.h"
|
||||
@@ -510,9 +509,9 @@ void VulkanDeferredLighting::render(vk::CommandBuffer cmd)
|
||||
header->invScreenWidth = 1.0f / gr_screen.max_w;
|
||||
header->invScreenHeight = 1.0f / gr_screen.max_h;
|
||||
header->nearPlane = gr_near_plane;
|
||||
shadows_fill_deferred_global_data(*header);
|
||||
|
||||
if (m_shadow->isInitialized() && Shadow_quality != ShadowQuality::Disabled) {
|
||||
vm_inverse_matrix4(&header->inv_view_matrix, &Shadow_view_matrix_render);
|
||||
shadow_cascade_params_bind_deferred();
|
||||
}
|
||||
}
|
||||
@@ -688,9 +687,6 @@ void VulkanDeferredLighting::render(vk::CommandBuffer cmd)
|
||||
if (rtShadowsActive) {
|
||||
lightShaderFlags |= SDR_FLAG_DEFERRED_RT_SHADOWS;
|
||||
}
|
||||
if (rtao_enabled()) {
|
||||
lightShaderFlags |= SDR_FLAG_DEFERRED_RTAO;
|
||||
}
|
||||
if (envMapAvailable) {
|
||||
lightShaderFlags |= SDR_FLAG_ENV_MAP;
|
||||
}
|
||||
@@ -758,10 +754,8 @@ void VulkanDeferredLighting::render(vk::CommandBuffer cmd)
|
||||
// Shadow map is depth-only, sampled with a depth-compare sampler
|
||||
// (sampler2DArrayShadow) for hardware PCF.
|
||||
vk::DescriptorImageInfo shadowTexInfo;
|
||||
vk::DescriptorImageInfo shadowRawTexInfo;
|
||||
if (m_shadow->isInitialized() && m_shadow->depthView()) {
|
||||
shadowTexInfo = {m_shadow->compareSampler(), m_shadow->depthView(), vk::ImageLayout::eShaderReadOnlyOptimal};
|
||||
shadowRawTexInfo = {m_shadow->rawSampler(), m_shadow->depthView(), vk::ImageLayout::eShaderReadOnlyOptimal};
|
||||
}
|
||||
|
||||
// Shadow cascade params (projection matrices/distances) are bound per-frame via
|
||||
@@ -807,7 +801,6 @@ void VulkanDeferredLighting::render(vk::CommandBuffer cmd)
|
||||
writer.setImage(GlobalBinding::EnvMap, envTexInfo);
|
||||
writer.setImage(GlobalBinding::IrradianceMap, irrTexInfo);
|
||||
writer.setBuffer(GlobalBinding::ShadowCascadeParams, shadowCascadeParamsInfo);
|
||||
writer.setImage(GlobalBinding::ShadowMapRaw, shadowRawTexInfo);
|
||||
|
||||
// Set 1: Material
|
||||
vk::DescriptorSet materialSet = descriptorMgr->allocateFrameSet(DescriptorSetIndex::Material);
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
#include "VulkanPostProcessing.h"
|
||||
|
||||
#include "gr_vulkan.h"
|
||||
#include "VulkanBarrier.h"
|
||||
#include "VulkanRenderer.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "graphics/matrix.h"
|
||||
#include "graphics/openxr.h"
|
||||
|
||||
namespace graphics::vulkan {
|
||||
|
||||
namespace {
|
||||
|
||||
struct MotionVectorData {
|
||||
matrix4 reprojection;
|
||||
float viewportRect[4];
|
||||
float sceneNear;
|
||||
float cockpitNear;
|
||||
float farPlane;
|
||||
float pad;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool VulkanMotionVectors::wanted()
|
||||
{
|
||||
// The attachment needs blend state of its own, and VR isn't supported yet.
|
||||
if (!getRendererInstance()->supportsIndependentBlend() || openxr_requested()) {
|
||||
return false;
|
||||
}
|
||||
return Gr_fsr_mode != FsrMode::Off && gr_is_capable(gr_capability::CAPABILITY_FSR);
|
||||
}
|
||||
|
||||
void VulkanMotionVectors::init(PostProcessContext& ctx)
|
||||
{
|
||||
m_ctx = &ctx;
|
||||
}
|
||||
|
||||
bool VulkanMotionVectors::createResources()
|
||||
{
|
||||
if (m_framebuffer) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_renderPass) {
|
||||
std::array<vk::AttachmentDescription, 2> attachments;
|
||||
std::array<vk::AttachmentReference, 2> colorRefs;
|
||||
for (uint32_t i = 0; i < attachments.size(); ++i) {
|
||||
attachments[i].format = i == 0 ? FORMAT : DEPTH_FORMAT;
|
||||
attachments[i].loadOp = vk::AttachmentLoadOp::eDontCare;
|
||||
attachments[i].storeOp = vk::AttachmentStoreOp::eStore;
|
||||
attachments[i].stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
|
||||
attachments[i].stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
|
||||
attachments[i].initialLayout = vk::ImageLayout::eUndefined;
|
||||
attachments[i].finalLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
|
||||
colorRefs[i].attachment = i;
|
||||
colorRefs[i].layout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
}
|
||||
|
||||
vk::SubpassDescription subpass;
|
||||
subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
|
||||
subpass.colorAttachmentCount = static_cast<uint32_t>(colorRefs.size());
|
||||
subpass.pColorAttachments = colorRefs.data();
|
||||
|
||||
vk::SubpassDependency dependency;
|
||||
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependency.dstSubpass = 0;
|
||||
dependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput
|
||||
| vk::PipelineStageFlagBits::eFragmentShader
|
||||
| vk::PipelineStageFlagBits::eComputeShader;
|
||||
dependency.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
||||
dependency.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eShaderRead;
|
||||
dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||
|
||||
vk::RenderPassCreateInfo rpInfo;
|
||||
rpInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
|
||||
rpInfo.pAttachments = attachments.data();
|
||||
rpInfo.subpassCount = 1;
|
||||
rpInfo.pSubpasses = &subpass;
|
||||
rpInfo.dependencyCount = 1;
|
||||
rpInfo.pDependencies = &dependency;
|
||||
|
||||
try {
|
||||
m_renderPass = m_ctx->device.createRenderPass(rpInfo);
|
||||
} catch (const vk::SystemError& e) {
|
||||
nprintf(("vulkan", "VulkanMotionVectors: Failed to create render pass: %s\n", e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t w = m_ctx->sceneExtent.width;
|
||||
const uint32_t h = m_ctx->sceneExtent.height;
|
||||
|
||||
const auto createTarget = [this, w, h](RenderTarget& target, vk::Format format, vk::ImageUsageFlags usage, vk::ImageAspectFlags aspect) {
|
||||
target.format = format;
|
||||
target.width = w;
|
||||
target.height = h;
|
||||
return m_ctx->createImage(w, h, format, usage, aspect, target.image, target.view, target.allocation);
|
||||
};
|
||||
|
||||
const auto renderable = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled;
|
||||
if (!createTarget(m_target, FORMAT, renderable, vk::ImageAspectFlagBits::eColor)
|
||||
|| !createTarget(m_drawnMotion, DRAWN_FORMAT, renderable, vk::ImageAspectFlagBits::eColor)
|
||||
|| !createTarget(m_depth, DEPTH_FORMAT, renderable, vk::ImageAspectFlagBits::eColor)) {
|
||||
nprintf(("vulkan", "VulkanMotionVectors: Failed to create image!\n"));
|
||||
destroyTargets();
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::array<vk::ImageView, 2> views = {m_target.view, m_depth.view};
|
||||
|
||||
vk::FramebufferCreateInfo fbInfo;
|
||||
fbInfo.renderPass = m_renderPass;
|
||||
fbInfo.attachmentCount = static_cast<uint32_t>(views.size());
|
||||
fbInfo.pAttachments = views.data();
|
||||
fbInfo.width = w;
|
||||
fbInfo.height = h;
|
||||
fbInfo.layers = 1;
|
||||
|
||||
try {
|
||||
m_framebuffer = m_ctx->device.createFramebuffer(fbInfo);
|
||||
} catch (const vk::SystemError& e) {
|
||||
nprintf(("vulkan", "VulkanMotionVectors: Failed to create framebuffer: %s\n", e.what()));
|
||||
destroyTargets();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanMotionVectors::destroyTargets()
|
||||
{
|
||||
if (!m_ctx || !m_ctx->device) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_framebuffer) {
|
||||
m_ctx->device.destroyFramebuffer(m_framebuffer);
|
||||
m_framebuffer = nullptr;
|
||||
}
|
||||
m_ctx->destroyTarget(m_target);
|
||||
m_ctx->destroyTarget(m_drawnMotion);
|
||||
m_ctx->destroyTarget(m_depth);
|
||||
}
|
||||
|
||||
void VulkanMotionVectors::shutdown()
|
||||
{
|
||||
if (!m_ctx || !m_ctx->device) {
|
||||
return;
|
||||
}
|
||||
|
||||
destroyTargets();
|
||||
|
||||
if (m_renderPass) {
|
||||
m_ctx->device.destroyRenderPass(m_renderPass);
|
||||
m_renderPass = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanMotionVectors::execute(vk::CommandBuffer cmd, vk::ImageView sceneDepthCopy)
|
||||
{
|
||||
GR_DEBUG_SCOPE("Motion vectors");
|
||||
|
||||
ImageBarrier2 barrier;
|
||||
barrier.image = m_drawnMotion.image;
|
||||
barrier.levelCount = 1;
|
||||
barrier.layerCount = 1;
|
||||
barrier.oldLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||
barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
barrier.srcStage = vk::PipelineStageFlagBits2::eColorAttachmentOutput;
|
||||
barrier.srcAccess = vk::AccessFlagBits2::eColorAttachmentWrite;
|
||||
barrier.dstStage = vk::PipelineStageFlagBits2::eFragmentShader;
|
||||
barrier.dstAccess = vk::AccessFlagBits2::eShaderSampledRead;
|
||||
cmdImageBarrier(cmd, barrier);
|
||||
|
||||
MotionVectorData data;
|
||||
matrix4 viewProj, previousViewProj, inverseViewProj;
|
||||
gr_get_velocity_camera(&viewProj, &previousViewProj, data.viewportRect);
|
||||
|
||||
// Not invertible until a scene has captured a camera
|
||||
if (vm_inverse_matrix4(&inverseViewProj, &viewProj)) {
|
||||
vm_matrix4_x_matrix4(&data.reprojection, &previousViewProj, &inverseViewProj);
|
||||
} else {
|
||||
vm_matrix4_set_identity(&data.reprojection);
|
||||
}
|
||||
|
||||
vk::Sampler nearestSampler = getTextureManager()->getSampler(
|
||||
vk::Filter::eNearest, vk::Filter::eNearest,
|
||||
vk::SamplerAddressMode::eClampToEdge, false, 0.0f, false);
|
||||
|
||||
data.sceneNear = Min_draw_distance;
|
||||
data.cockpitNear = Min_draw_distance_cockpit;
|
||||
data.farPlane = Max_draw_distance;
|
||||
data.pad = 0.0f;
|
||||
|
||||
const std::array<vk::ImageView, 2> inputs = {sceneDepthCopy, m_drawnMotion.view};
|
||||
m_ctx->drawFullscreenTriangleMulti(cmd, m_renderPass, m_framebuffer, m_ctx->sceneExtent,
|
||||
SDR_TYPE_MOTION_VECTORS, inputs.data(), static_cast<uint32_t>(inputs.size()), &data, sizeof(data), nearestSampler, 2);
|
||||
}
|
||||
|
||||
} // namespace graphics::vulkan
|
||||
@@ -64,15 +64,15 @@ bool VulkanSMAA::init(PostProcessContext& ctx, const VulkanLDR& ldr)
|
||||
|
||||
m_initialized = true;
|
||||
nprintf(("vulkan", "VulkanSMAA: SMAA initialized (%ux%u)\n",
|
||||
m_ctx->outputExtent.width, m_ctx->outputExtent.height));
|
||||
m_ctx->sceneExtent.width, m_ctx->sceneExtent.height));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanSMAA::createTargets()
|
||||
{
|
||||
const vk::Format ldrFormat = m_ctx->ldrFormat;
|
||||
const uint32_t w = m_ctx->outputExtent.width;
|
||||
const uint32_t h = m_ctx->outputExtent.height;
|
||||
const uint32_t w = m_ctx->sceneExtent.width;
|
||||
const uint32_t h = m_ctx->sceneExtent.height;
|
||||
|
||||
if (!m_ctx->createImage(w, h, ldrFormat,
|
||||
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled,
|
||||
@@ -194,8 +194,8 @@ void VulkanSMAA::execute(vk::CommandBuffer cmd)
|
||||
GR_DEBUG_SCOPE("SMAA");
|
||||
|
||||
graphics::generic_data::smaa_data smaaData;
|
||||
smaaData.smaa_rt_metrics.x = static_cast<float>(m_ctx->outputExtent.width);
|
||||
smaaData.smaa_rt_metrics.y = static_cast<float>(m_ctx->outputExtent.height);
|
||||
smaaData.smaa_rt_metrics.x = static_cast<float>(m_ctx->sceneExtent.width);
|
||||
smaaData.smaa_rt_metrics.y = static_cast<float>(m_ctx->sceneExtent.height);
|
||||
smaaData.pad[0] = 0.0f;
|
||||
smaaData.pad[1] = 0.0f;
|
||||
|
||||
@@ -210,7 +210,7 @@ void VulkanSMAA::execute(vk::CommandBuffer cmd)
|
||||
// the render pass's own loadOp=eDontCare, which leaves those pixels undefined.
|
||||
static const std::array<float, 4> kClearColor = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
m_ctx->drawFullscreenTriangle(cmd, m_ldr->renderPass(),
|
||||
m_smaaEdgesFB, m_ctx->outputExtent,
|
||||
m_smaaEdgesFB, m_ctx->sceneExtent,
|
||||
SDR_TYPE_POST_PROCESS_SMAA_EDGE,
|
||||
m_ldr->getAADetectionView(), m_ctx->linearSampler,
|
||||
&smaaData, sizeof(smaaData),
|
||||
@@ -224,7 +224,7 @@ void VulkanSMAA::execute(vk::CommandBuffer cmd)
|
||||
GR_DEBUG_SCOPE("SMAA Blending Weights calculation");
|
||||
|
||||
std::array<vk::ImageView, 3> views = {m_smaaEdges.view, m_smaaAreaTexView, m_smaaSearchTexView};
|
||||
m_ctx->drawFullscreenTriangleMulti(cmd, m_ldr->renderPass(), m_smaaBlendFB, m_ctx->outputExtent,
|
||||
m_ctx->drawFullscreenTriangleMulti(cmd, m_ldr->renderPass(), m_smaaBlendFB, m_ctx->sceneExtent,
|
||||
SDR_TYPE_POST_PROCESS_SMAA_BLENDING_WEIGHT, views.data(), static_cast<uint32_t>(views.size()),
|
||||
&smaaData, sizeof(smaaData));
|
||||
}
|
||||
@@ -235,7 +235,7 @@ void VulkanSMAA::execute(vk::CommandBuffer cmd)
|
||||
GR_DEBUG_SCOPE("SMAA Neighborhood Blending");
|
||||
|
||||
std::array<vk::ImageView, 2> views = {m_ldr->ldrView(), m_smaaBlend.view};
|
||||
m_ctx->drawFullscreenTriangleMulti(cmd, m_ldr->renderPass(), m_ldr->luminanceFramebuffer(), m_ctx->outputExtent,
|
||||
m_ctx->drawFullscreenTriangleMulti(cmd, m_ldr->renderPass(), m_ldr->luminanceFramebuffer(), m_ctx->sceneExtent,
|
||||
SDR_TYPE_POST_PROCESS_SMAA_NEIGHBORHOOD_BLENDING, views.data(), static_cast<uint32_t>(views.size()),
|
||||
&smaaData, sizeof(smaaData));
|
||||
}
|
||||
@@ -245,7 +245,7 @@ void VulkanSMAA::execute(vk::CommandBuffer cmd)
|
||||
GR_DEBUG_SCOPE("SMAA Resolve");
|
||||
|
||||
m_ctx->drawFullscreenTriangle(cmd, m_ldr->renderPass(),
|
||||
m_ldr->ldrFramebuffer(), m_ctx->outputExtent,
|
||||
m_ldr->ldrFramebuffer(), m_ctx->sceneExtent,
|
||||
SDR_TYPE_POST_PROCESS_SMAA_RESOLVE,
|
||||
m_ldr->luminanceView(), m_ctx->linearSampler,
|
||||
nullptr, 0,
|
||||
|
||||
@@ -34,7 +34,14 @@ bool VulkanShadowMap::init(PostProcessContext& ctx)
|
||||
return false;
|
||||
}
|
||||
|
||||
const int size = shadows_map_resolution();
|
||||
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 = 512; break;
|
||||
}
|
||||
|
||||
const auto layers = static_cast<uint32_t>(Num_shadow_cascades + Num_cockpit_shadow_cascades);
|
||||
nprintf(("vulkan", "VulkanPostProcessor: Creating %dx%d shadow map (%d cascades)\n", size, size, layers));
|
||||
@@ -181,32 +188,6 @@ bool VulkanShadowMap::init(PostProcessContext& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// Raw (non-compare) sampler on the same depth view, for the PCSS blocker search --
|
||||
// it needs actual stored depth values, not a compare result. GL_NEAREST-equivalent
|
||||
// filtering: the blocker search wants the exact per-texel depth, not a hardware-
|
||||
// filtered blend across the compare boundary (deliberately different from the compare
|
||||
// sampler's linear filtering above). Vulkan samplers are independent of images, so this
|
||||
// is always available -- no capability gate needed here (contrast the OpenGL backend,
|
||||
// which needs GL 3.3 because compare mode there is texture-object state).
|
||||
{
|
||||
vk::SamplerCreateInfo samplerInfo;
|
||||
samplerInfo.magFilter = vk::Filter::eNearest;
|
||||
samplerInfo.minFilter = vk::Filter::eNearest;
|
||||
samplerInfo.mipmapMode = vk::SamplerMipmapMode::eNearest;
|
||||
samplerInfo.addressModeU = vk::SamplerAddressMode::eClampToEdge;
|
||||
samplerInfo.addressModeV = vk::SamplerAddressMode::eClampToEdge;
|
||||
samplerInfo.addressModeW = vk::SamplerAddressMode::eClampToEdge;
|
||||
samplerInfo.compareEnable = VK_FALSE;
|
||||
samplerInfo.borderColor = vk::BorderColor::eFloatOpaqueWhite;
|
||||
|
||||
try {
|
||||
m_rawSampler = m_ctx->device.createSampler(samplerInfo);
|
||||
} catch (const vk::SystemError& e) {
|
||||
nprintf(("vulkan", "VulkanPostProcessor: Failed to create shadow raw sampler: %s\n", e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_textureSize = size;
|
||||
m_initialized = true;
|
||||
nprintf(("vulkan", "VulkanPostProcessor: Shadow map initialized (%dx%d, %d cascades)\n", size, size, layers));
|
||||
@@ -223,10 +204,6 @@ void VulkanShadowMap::shutdown()
|
||||
m_ctx->device.destroySampler(m_compareSampler);
|
||||
m_compareSampler = nullptr;
|
||||
}
|
||||
if (m_rawSampler) {
|
||||
m_ctx->device.destroySampler(m_rawSampler);
|
||||
m_rawSampler = nullptr;
|
||||
}
|
||||
if (m_framebuffer) {
|
||||
m_ctx->device.destroyFramebuffer(m_framebuffer);
|
||||
m_framebuffer = nullptr;
|
||||
|
||||
@@ -119,15 +119,6 @@ public:
|
||||
*/
|
||||
bool isEnabled() const { return m_enabled; }
|
||||
|
||||
/**
|
||||
* @brief Whether buildTlas() already ran in the current frame. game_render_frame() can run
|
||||
* more than once in a frame (for example, one time for each OpenXR eye), but one build in
|
||||
* each frame is sufficient.
|
||||
*/
|
||||
bool isTlasBuiltThisFrame() const {
|
||||
return m_bufferManager != nullptr && m_lastTlasBuildFrame == m_bufferManager->getCurrentFrameNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief A permanent, 0-instance TLAS built once at init(). Used as a
|
||||
* valid (non-null) descriptor value in place of getTlasForShaderBinding()
|
||||
@@ -284,10 +275,6 @@ private:
|
||||
// gated behind m_enabled/m_initialized so this only matters defensively.
|
||||
uint32_t currentFrameIndex() const { return m_bufferManager != nullptr ? m_bufferManager->getCurrentFrame() : 0; }
|
||||
|
||||
// Monotonic frame number of the last buildTlas() that ran. buildTlas() uses it to build
|
||||
// only one time in each frame. See isTlasBuiltThisFrame().
|
||||
uint64_t m_lastTlasBuildFrame = UINT64_MAX;
|
||||
|
||||
vk::Device m_device;
|
||||
vk::PhysicalDevice m_physicalDevice;
|
||||
VulkanMemoryManager* m_memoryManager = nullptr;
|
||||
|
||||
@@ -425,12 +425,6 @@ void VulkanRaytracingManager::buildTlas()
|
||||
return;
|
||||
}
|
||||
|
||||
// Build only one time in each frame. See isTlasBuiltThisFrame().
|
||||
if (isTlasBuiltThisFrame()) {
|
||||
return;
|
||||
}
|
||||
m_lastTlasBuildFrame = m_bufferManager->getCurrentFrameNumber();
|
||||
|
||||
// Each frame-in-flight slot owns its own instance/TLAS/scratch buffers (see
|
||||
// FrameTlasResources' declaration for why a single shared set would race
|
||||
// across overlapping in-flight frames).
|
||||
|
||||
@@ -50,7 +50,7 @@ void VulkanRenderer::createCompositionResources()
|
||||
vk::ImageCreateInfo imageInfo;
|
||||
imageInfo.imageType = vk::ImageType::e2D;
|
||||
imageInfo.format = HDR_COLOR_FORMAT;
|
||||
imageInfo.extent = vk::Extent3D(m_compositionExtent.width, m_compositionExtent.height, 1);
|
||||
imageInfo.extent = vk::Extent3D(m_renderExtent.width, m_renderExtent.height, 1);
|
||||
imageInfo.mipLevels = 1;
|
||||
imageInfo.arrayLayers = 1;
|
||||
imageInfo.samples = vk::SampleCountFlagBits::e1;
|
||||
@@ -155,8 +155,8 @@ void VulkanRenderer::createFrameBuffers()
|
||||
framebufferInfo.renderPass = m_renderPass.get();
|
||||
framebufferInfo.attachmentCount = 2;
|
||||
framebufferInfo.pAttachments = attachments;
|
||||
framebufferInfo.width = m_compositionExtent.width;
|
||||
framebufferInfo.height = m_compositionExtent.height;
|
||||
framebufferInfo.width = m_renderExtent.width;
|
||||
framebufferInfo.height = m_renderExtent.height;
|
||||
framebufferInfo.layers = 1;
|
||||
|
||||
m_swapChainFramebuffers.push_back(m_device->createFramebufferUnique(framebufferInfo));
|
||||
@@ -265,8 +265,8 @@ void VulkanRenderer::createDepthResources()
|
||||
vk::ImageCreateInfo imageInfo;
|
||||
imageInfo.imageType = vk::ImageType::e2D;
|
||||
imageInfo.format = m_depthFormat;
|
||||
imageInfo.extent.width = m_compositionExtent.width;
|
||||
imageInfo.extent.height = m_compositionExtent.height;
|
||||
imageInfo.extent.width = m_renderExtent.width;
|
||||
imageInfo.extent.height = m_renderExtent.height;
|
||||
imageInfo.extent.depth = 1;
|
||||
imageInfo.mipLevels = 1;
|
||||
imageInfo.arrayLayers = 1;
|
||||
@@ -296,7 +296,7 @@ void VulkanRenderer::createDepthResources()
|
||||
m_depthImageView = m_device->createImageViewUnique(viewInfo);
|
||||
|
||||
nprintf(("vulkan", "Vulkan: Created depth buffer (%dx%d, format %d)\n",
|
||||
m_compositionExtent.width, m_compositionExtent.height, static_cast<int>(m_depthFormat)));
|
||||
m_renderExtent.width, m_renderExtent.height, static_cast<int>(m_depthFormat)));
|
||||
}
|
||||
void VulkanRenderer::destroyDepthResources()
|
||||
{
|
||||
@@ -472,8 +472,8 @@ bool VulkanRenderer::readbackFramebuffer(ubyte** outPixels, uint32_t* outWidth,
|
||||
const vk::PipelineStageFlags2 srcStageMask = vk::PipelineStageFlagBits2::eFragmentShader;
|
||||
const vk::AccessFlags2 srcAccessMask = vk::AccessFlagBits2::eShaderSampledRead;
|
||||
const uint32_t bytesPerSrcPixel = 8; // fp16 RGBA = 4 x 2 bytes
|
||||
uint32_t w = m_compositionExtent.width;
|
||||
uint32_t h = m_compositionExtent.height;
|
||||
uint32_t w = m_renderExtent.width;
|
||||
uint32_t h = m_renderExtent.height;
|
||||
vk::DeviceSize bufferSize = static_cast<vk::DeviceSize>(w) * h * bytesPerSrcPixel;
|
||||
|
||||
// End the current render pass so we can record transfer commands
|
||||
|
||||
@@ -184,7 +184,6 @@ class VulkanRenderer {
|
||||
vk::CommandBuffer getCurrentCommandBuffer() const { return m_currentCommandBuffer; }
|
||||
|
||||
vk::Instance getVkInstance() const { return m_vkInstance.get(); }
|
||||
bool supportsIndependentBlend() const { return m_deviceFeatures.independentBlend == VK_TRUE; }
|
||||
vk::PhysicalDevice getPhysicalDevice() const { return m_physicalDevice; }
|
||||
vk::Device getDevice() const { return m_device.get(); }
|
||||
vk::Queue getGraphicsQueue() const { return m_graphicsQueue; }
|
||||
@@ -207,9 +206,6 @@ class VulkanRenderer {
|
||||
*/
|
||||
vk::Extent2D getRenderExtent() const { return m_renderExtent; }
|
||||
|
||||
// The render extent times FSR's upscale factor. Everything after the scene draws at this size.
|
||||
vk::Extent2D getCompositionExtent() const { return m_compositionExtent; }
|
||||
|
||||
/**
|
||||
* @brief Get the composition image the frame is currently being rendered into
|
||||
*
|
||||
@@ -468,11 +464,6 @@ class VulkanRenderer {
|
||||
bool recreateSwapChain();
|
||||
bool shouldRecreateSwapChain(SwapChainStatus status) const;
|
||||
|
||||
vk::Extent2D wantedCompositionExtent() const;
|
||||
void setCompositionExtent(vk::Extent2D extent);
|
||||
// Recreates the composition and output-sized targets when the FSR upscale factor changed. Outside a frame.
|
||||
void updateCompositionExtent();
|
||||
|
||||
void createImGuiDescriptorPool();
|
||||
void initImGui();
|
||||
void shutdownImGui();
|
||||
@@ -505,7 +496,6 @@ class VulkanRenderer {
|
||||
// option), so treating the two as one is what put whole frames in the
|
||||
// upper-left corner of the screen.
|
||||
vk::Extent2D m_renderExtent;
|
||||
vk::Extent2D m_compositionExtent;
|
||||
SCP_vector<vk::Image> m_swapChainImages;
|
||||
SCP_vector<vk::UniqueImageView> m_swapChainImageViews;
|
||||
SCP_vector<vk::UniqueFramebuffer> m_swapChainFramebuffers;
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
#include "VulkanBarrier.h"
|
||||
#include "VulkanBuffer.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "VulkanUpscaler.h"
|
||||
|
||||
|
||||
#include "backends/imgui_impl_vulkan.h"
|
||||
#include "graphics/2d.h"
|
||||
#include "graphics/matrix.h"
|
||||
#include "lighting/lighting.h"
|
||||
|
||||
extern float flFrametime;
|
||||
@@ -28,11 +26,6 @@ void VulkanRenderer::beginTrackedRenderPass(const PassBeginDesc& desc)
|
||||
m_currentCommandBuffer.beginRenderPass(rpBegin, vk::SubpassContents::eInline);
|
||||
|
||||
m_stateTracker->setRenderPass(desc.renderPass, 0);
|
||||
if (desc.renderPass == m_renderPass.get() || desc.renderPass == m_renderPassLoad.get()) {
|
||||
// Everything the engine draws onto the composition image is in gr_screen units
|
||||
m_stateTracker->setScreenScale(static_cast<float>(m_compositionExtent.width) / static_cast<float>(m_renderExtent.width),
|
||||
static_cast<float>(m_compositionExtent.height) / static_cast<float>(m_renderExtent.height));
|
||||
}
|
||||
m_stateTracker->setColorAttachmentCount(desc.colorAttachmentCount);
|
||||
m_stateTracker->setCurrentSampleCount(desc.sampleCount);
|
||||
m_stateTracker->setRenderArea(desc.extent);
|
||||
@@ -101,48 +94,6 @@ void VulkanRenderer::acquireNextSwapChainImage()
|
||||
// Reserve the image as in use
|
||||
m_swapChainImageRenderImage[m_currentSwapChainImage] = m_frames[m_currentFrame].get();
|
||||
}
|
||||
vk::Extent2D VulkanRenderer::wantedCompositionExtent() const
|
||||
{
|
||||
const float factor = vulkan_upscaler_usable() ? gr_fsr_upscale_factor(Gr_fsr_mode) : 1.0f;
|
||||
return {static_cast<uint32_t>(std::lround(m_renderExtent.width * factor)),
|
||||
static_cast<uint32_t>(std::lround(m_renderExtent.height * factor))};
|
||||
}
|
||||
|
||||
void VulkanRenderer::setCompositionExtent(vk::Extent2D extent)
|
||||
{
|
||||
m_compositionExtent = extent;
|
||||
Gr_composition_scale = static_cast<float>(m_compositionExtent.width) / static_cast<float>(m_renderExtent.width);
|
||||
}
|
||||
|
||||
void VulkanRenderer::updateCompositionExtent()
|
||||
{
|
||||
const auto wanted = wantedCompositionExtent();
|
||||
if (wanted == m_compositionExtent) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_device->waitIdle();
|
||||
setCompositionExtent(wanted);
|
||||
mprintf(("Vulkan: Composition resolution %ux%u for render resolution %ux%u\n",
|
||||
m_compositionExtent.width, m_compositionExtent.height, m_renderExtent.width, m_renderExtent.height));
|
||||
|
||||
destroyDepthResources();
|
||||
createDepthResources();
|
||||
createCompositionResources();
|
||||
createFrameBuffers();
|
||||
|
||||
if (m_postProcessor && !m_postProcessor->resize(m_renderExtent, m_compositionExtent)) {
|
||||
mprintf(("Vulkan: post-processor resize failed, disabling post-processing!\n"));
|
||||
setPostProcessor(nullptr);
|
||||
m_postProcessor->shutdown();
|
||||
m_postProcessor.reset();
|
||||
}
|
||||
if (m_drawManager) {
|
||||
m_drawManager->onResize();
|
||||
}
|
||||
m_previousSwapChainImage = UINT32_MAX;
|
||||
}
|
||||
|
||||
void VulkanRenderer::setupFrame()
|
||||
{
|
||||
if (m_frameInProgress) {
|
||||
@@ -158,8 +109,6 @@ void VulkanRenderer::setupFrame()
|
||||
m_pipelinesStale = false;
|
||||
}
|
||||
|
||||
updateCompositionExtent();
|
||||
|
||||
// Free completed texture upload command buffers
|
||||
Assertion(m_textureManager, "Vulkan TextureManager not initialized in setupFrame!");
|
||||
m_textureManager->frameStart();
|
||||
@@ -212,7 +161,7 @@ void VulkanRenderer::setupFrame()
|
||||
PassBeginDesc pass;
|
||||
pass.renderPass = m_renderPass.get();
|
||||
pass.framebuffer = m_swapChainFramebuffers[m_currentSwapChainImage].get();
|
||||
pass.extent = m_compositionExtent;
|
||||
pass.extent = m_renderExtent;
|
||||
pass.clearValues = clearValues;
|
||||
beginTrackedRenderPass(pass);
|
||||
|
||||
@@ -324,12 +273,8 @@ void VulkanRenderer::beginSceneRendering()
|
||||
return;
|
||||
}
|
||||
|
||||
// End the current swap chain render pass. vulkan_scene_texture_begin() can end it before
|
||||
// this call, for the TLAS build, so end it only when it is still open.
|
||||
endCurrentRenderPass();
|
||||
|
||||
m_postProcessor->prepareMotionVectors();
|
||||
m_postProcessor->prepareUpscaler();
|
||||
// End the current swap chain render pass
|
||||
m_currentCommandBuffer.endRenderPass();
|
||||
|
||||
// Use G-buffer render pass when deferred lighting is enabled and G-buffer is ready
|
||||
m_useGbufRenderPass = m_postProcessor->deferred().isInitialized() && light_deferred_enabled();
|
||||
@@ -339,33 +284,29 @@ void VulkanRenderer::beginSceneRendering()
|
||||
pass.extent = m_postProcessor->getSceneExtent();
|
||||
|
||||
if (m_useGbufRenderPass) {
|
||||
const auto& gbuffer = m_postProcessor->deferred();
|
||||
|
||||
// Clear values: 6 color, optionally motion vectors, then depth. Extras are ignored by the plain pass.
|
||||
std::array<vk::ClearValue, VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT + 2> clearValues{};
|
||||
// Clear values: 6 color + depth
|
||||
std::array<vk::ClearValue, VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT + 1> clearValues{};
|
||||
clearValues[VulkanDeferredGBuffer::GBUF_ATT_COLOR].color.setFloat32({0.0f, 0.0f, 0.0f, 1.0f});
|
||||
clearValues[VulkanDeferredGBuffer::GBUF_ATT_POSITION].color.setFloat32({0.0f, 0.0f, 0.0f, 0.0f});
|
||||
clearValues[VulkanDeferredGBuffer::GBUF_ATT_NORMAL].color.setFloat32({0.0f, 0.0f, 0.0f, 0.0f});
|
||||
clearValues[VulkanDeferredGBuffer::GBUF_ATT_SPECULAR].color.setFloat32({0.0f, 0.0f, 0.0f, 0.0f});
|
||||
clearValues[VulkanDeferredGBuffer::GBUF_ATT_EMISSIVE].color.setFloat32({0.0f, 0.0f, 0.0f, 0.0f});
|
||||
clearValues[VulkanDeferredGBuffer::GBUF_ATT_COMPOSITE].color.setFloat32({0.0f, 0.0f, 0.0f, 0.0f});
|
||||
if (gbuffer.velocityActive()) {
|
||||
clearValues[VulkanDeferredGBuffer::velocityAttachmentIndex()].color.setFloat32({VulkanMotionVectors::NO_MOTION_WRITTEN, 0.0f, 0.0f, 0.0f});
|
||||
}
|
||||
clearValues[gbuffer.depthAttachmentIndex()].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
clearValues[VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
|
||||
pass.renderPass = gbuffer.renderPass();
|
||||
pass.framebuffer = gbuffer.framebuffer();
|
||||
pass.renderPass = m_postProcessor->deferred().renderPass();
|
||||
pass.framebuffer = m_postProcessor->deferred().framebuffer();
|
||||
pass.clearValues = clearValues;
|
||||
pass.colorAttachmentCount = gbuffer.colorAttachmentCount();
|
||||
pass.colorAttachmentCount = VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT;
|
||||
beginTrackedRenderPass(pass);
|
||||
} else {
|
||||
auto clearValues = m_postProcessor->sceneClearValues();
|
||||
std::array<vk::ClearValue, 2> clearValues;
|
||||
clearValues[0].color.setFloat32({0.0f, 0.0f, 0.0f, 1.0f});
|
||||
clearValues[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
|
||||
pass.renderPass = m_postProcessor->getSceneRenderPass();
|
||||
pass.framebuffer = m_postProcessor->getSceneFramebuffer();
|
||||
pass.clearValues = clearValues;
|
||||
pass.colorAttachmentCount = m_postProcessor->sceneColorAttachmentCount();
|
||||
beginTrackedRenderPass(pass);
|
||||
}
|
||||
|
||||
@@ -378,7 +319,6 @@ void VulkanRenderer::resumeSceneRendering()
|
||||
pass.renderPass = m_postProcessor->getSceneRenderPassLoad();
|
||||
pass.framebuffer = m_postProcessor->getSceneFramebuffer();
|
||||
pass.extent = m_postProcessor->getSceneExtent();
|
||||
pass.colorAttachmentCount = m_postProcessor->sceneColorAttachmentCount();
|
||||
beginTrackedRenderPass(pass);
|
||||
}
|
||||
|
||||
@@ -394,12 +334,6 @@ void VulkanRenderer::endSceneRendering()
|
||||
// End HDR scene render pass (transitions scene color to eShaderReadOnlyOptimal)
|
||||
m_currentCommandBuffer.endRenderPass();
|
||||
|
||||
// Nothing drawn after the scene, like the HUD, has motion vectors
|
||||
Gr_motion_vectors_active = false;
|
||||
|
||||
m_postProcessor->executeMotionVectors(m_currentCommandBuffer);
|
||||
m_postProcessor->executeUpscaler(m_currentCommandBuffer);
|
||||
|
||||
// Update distortion ping-pong textures (every ~30ms, matching OpenGL)
|
||||
if (Gr_framebuffer_effects.any_set()) {
|
||||
m_postProcessor->updateDistortion(m_currentCommandBuffer, flFrametime);
|
||||
@@ -423,7 +357,7 @@ void VulkanRenderer::endSceneRendering()
|
||||
PassBeginDesc pass;
|
||||
pass.renderPass = m_renderPassLoad.get();
|
||||
pass.framebuffer = m_swapChainFramebuffers[m_currentSwapChainImage].get();
|
||||
pass.extent = m_compositionExtent;
|
||||
pass.extent = m_renderExtent;
|
||||
pass.clearValues = clearValues;
|
||||
pass.viewport = PassViewport::NoFlip;
|
||||
beginTrackedRenderPass(pass);
|
||||
@@ -433,9 +367,9 @@ void VulkanRenderer::endSceneRendering()
|
||||
|
||||
// Restore Y-flipped viewport for HUD rendering
|
||||
m_stateTracker->setViewport(0.0f,
|
||||
static_cast<float>(m_compositionExtent.height),
|
||||
static_cast<float>(m_compositionExtent.width),
|
||||
-static_cast<float>(m_compositionExtent.height));
|
||||
static_cast<float>(m_renderExtent.height),
|
||||
static_cast<float>(m_renderExtent.width),
|
||||
-static_cast<float>(m_renderExtent.height));
|
||||
|
||||
m_sceneRendering = false;
|
||||
m_useGbufRenderPass = false;
|
||||
@@ -474,24 +408,23 @@ void VulkanRenderer::resumeScenePassAfterCopy()
|
||||
pass.extent = m_postProcessor->getSceneExtent();
|
||||
|
||||
if (m_useGbufRenderPass) {
|
||||
const auto& gbuffer = m_postProcessor->deferred();
|
||||
|
||||
// Clear values ignored for eLoad but array must cover all attachments
|
||||
std::array<vk::ClearValue, VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT + 2> clearValues{};
|
||||
clearValues[gbuffer.depthAttachmentIndex()].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
std::array<vk::ClearValue, VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT + 1> clearValues{};
|
||||
clearValues[VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
|
||||
pass.renderPass = gbuffer.renderPassLoad();
|
||||
pass.framebuffer = gbuffer.framebuffer();
|
||||
pass.renderPass = m_postProcessor->deferred().renderPassLoad();
|
||||
pass.framebuffer = m_postProcessor->deferred().framebuffer();
|
||||
pass.clearValues = clearValues;
|
||||
pass.colorAttachmentCount = gbuffer.colorAttachmentCount();
|
||||
pass.colorAttachmentCount = VulkanDeferredGBuffer::GBUF_COLOR_ATTACHMENT_COUNT;
|
||||
beginTrackedRenderPass(pass);
|
||||
} else {
|
||||
auto clearValues = m_postProcessor->sceneClearValues();
|
||||
std::array<vk::ClearValue, 2> clearValues;
|
||||
clearValues[0].color.setFloat32({0.0f, 0.0f, 0.0f, 1.0f});
|
||||
clearValues[1].depthStencil = vk::ClearDepthStencilValue(1.0f, 0);
|
||||
|
||||
pass.renderPass = m_postProcessor->getSceneRenderPassLoad();
|
||||
pass.framebuffer = m_postProcessor->getSceneFramebuffer();
|
||||
pass.clearValues = clearValues;
|
||||
pass.colorAttachmentCount = m_postProcessor->sceneColorAttachmentCount();
|
||||
beginTrackedRenderPass(pass);
|
||||
}
|
||||
}
|
||||
@@ -678,7 +611,7 @@ void VulkanRenderer::resumeSwapChainPass()
|
||||
PassBeginDesc pass;
|
||||
pass.renderPass = m_renderPassLoad.get();
|
||||
pass.framebuffer = m_swapChainFramebuffers[m_currentSwapChainImage].get();
|
||||
pass.extent = m_compositionExtent;
|
||||
pass.extent = m_renderExtent;
|
||||
pass.clearValues = clearValues;
|
||||
beginTrackedRenderPass(pass);
|
||||
}
|
||||
@@ -692,7 +625,7 @@ void VulkanRenderer::restartCompositionPass()
|
||||
PassBeginDesc pass;
|
||||
pass.renderPass = m_renderPass.get();
|
||||
pass.framebuffer = m_swapChainFramebuffers[m_currentSwapChainImage].get();
|
||||
pass.extent = m_compositionExtent;
|
||||
pass.extent = m_renderExtent;
|
||||
pass.clearValues = clearValues;
|
||||
beginTrackedRenderPass(pass);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "VulkanBuffer.h"
|
||||
#include "VulkanOpenXR.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "VulkanUpscaler.h"
|
||||
|
||||
#include "cmdline/cmdline.h"
|
||||
#include "globalincs/version.h"
|
||||
@@ -536,10 +535,6 @@ bool VulkanRenderer::initialize()
|
||||
m_renderExtent, m_depthFormat, m_hdrActive)) {
|
||||
mprintf(("Warning: Failed to initialize Vulkan post-processor, post-processing will be disabled\n"));
|
||||
m_postProcessor.reset();
|
||||
} else if (!m_postProcessor->resize(m_renderExtent, m_compositionExtent)) {
|
||||
mprintf(("Warning: Failed to size the Vulkan post-processor, post-processing will be disabled\n"));
|
||||
m_postProcessor->shutdown();
|
||||
m_postProcessor.reset();
|
||||
} else {
|
||||
setPostProcessor(m_postProcessor.get());
|
||||
}
|
||||
@@ -1062,15 +1057,6 @@ bool VulkanRenderer::createLogicalDevice(const PhysicalDeviceValues& deviceValue
|
||||
deviceCreateChain.unlink<vk::PhysicalDeviceBufferDeviceAddressFeatures>();
|
||||
}
|
||||
|
||||
// FSR adds to the feature set audited above, for AMD's shaders and host code. It's disabled in VR for now.
|
||||
if (!openxr_requested()) {
|
||||
vulkan_upscaler_prepare_device(m_vkInstance.get(),
|
||||
deviceValues.device,
|
||||
deviceValues.graphicsQueueIndex.index,
|
||||
supportedFeatures,
|
||||
deviceCreateChain.get<vk::DeviceCreateInfo>());
|
||||
}
|
||||
|
||||
if (openxr_requested())
|
||||
m_device = vulkan_openxr_create_device(deviceValues.device, deviceCreateChain.get<vk::DeviceCreateInfo>());
|
||||
else
|
||||
@@ -1078,7 +1064,6 @@ bool VulkanRenderer::createLogicalDevice(const PhysicalDeviceValues& deviceValue
|
||||
|
||||
// Load device-level function pointers for the dynamic dispatcher
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.init(m_device.get());
|
||||
vulkan_upscaler_device_created(m_device.get());
|
||||
|
||||
// Create queues
|
||||
m_graphicsQueue = m_device->getQueue(deviceValues.graphicsQueueIndex.index, 0);
|
||||
@@ -1196,7 +1181,6 @@ bool VulkanRenderer::createSwapChain(const PhysicalDeviceValues& deviceValues, v
|
||||
if (!Cmdline_window_res) {
|
||||
m_renderExtent = vk::Extent2D(static_cast<uint32_t>(gr_screen.max_w), static_cast<uint32_t>(gr_screen.max_h));
|
||||
}
|
||||
setCompositionExtent(wantedCompositionExtent());
|
||||
|
||||
mprintf(("Vulkan: Swap chain output mode: %s\n", m_hdrActive ? "HDR10 (PQ/BT.2020)" : "SDR (sRGB)"));
|
||||
mprintf(("Vulkan: Render resolution %ux%u, window/swap chain %ux%u\n",
|
||||
@@ -1341,7 +1325,7 @@ bool VulkanRenderer::recreateSwapChain()
|
||||
// Recreate the post-processor's extent-sized targets (scene color/depth,
|
||||
// G-buffer, bloom chains, LDR/SMAA targets, ...). Its render passes and
|
||||
// samplers are extent-independent and stay alive, keeping pipelines valid.
|
||||
if (m_postProcessor && !m_postProcessor->resize(m_renderExtent, m_compositionExtent)) {
|
||||
if (m_postProcessor && !m_postProcessor->resize(m_renderExtent)) {
|
||||
mprintf(("Vulkan: post-processor resize failed, disabling post-processing!\n"));
|
||||
setPostProcessor(nullptr);
|
||||
m_postProcessor->shutdown();
|
||||
|
||||
@@ -8,16 +8,6 @@ namespace graphics::vulkan {
|
||||
// Global shader manager pointer
|
||||
static VulkanShaderManager* g_shaderManager = nullptr;
|
||||
|
||||
static uint32_t vertex_input_mask(const ShaderTypeInfo* typeInfo)
|
||||
{
|
||||
uint32_t mask = shader_vertex_input_mask(*typeInfo);
|
||||
// Batched effects also read their motion, which the attribute lists shared with OpenGL can't hold
|
||||
if (typeInfo->type_id == SDR_TYPE_BATCHED_BITMAP || typeInfo->type_id == SDR_TYPE_EFFECT_PARTICLE) {
|
||||
mask |= (1u << VATTRIB_EFFECT_MOTION);
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
VulkanShaderManager* getShaderManager()
|
||||
{
|
||||
Assertion(g_shaderManager != nullptr, "Vulkan ShaderManager not initialized!");
|
||||
@@ -140,7 +130,7 @@ void VulkanShaderManager::recompileAllShaders(const std::function<void(size_t, s
|
||||
nprintf(("vulkan", "VulkanShaderManager: Failed to create vertex module: %s\n", e.what()));
|
||||
}
|
||||
}
|
||||
shader.vertexInputMask = vertex_input_mask(typeInfo);
|
||||
shader.vertexInputMask = shader_vertex_input_mask(*typeInfo);
|
||||
|
||||
SCP_string fragFile = typeInfo->frag;
|
||||
auto fragSpirv = m_compiler->compile(fragFile, vk::ShaderStageFlagBits::eFragment, type, flags, requiresRaytracing);
|
||||
@@ -232,7 +222,7 @@ int VulkanShaderManager::loadShader(shader_type type, unsigned int flags)
|
||||
vertFile.c_str(), e.what()));
|
||||
}
|
||||
}
|
||||
shader.vertexInputMask = vertex_input_mask(typeInfo);
|
||||
shader.vertexInputMask = shader_vertex_input_mask(*typeInfo);
|
||||
|
||||
// Compile fragment shader
|
||||
SCP_string fragFile = typeInfo->frag;
|
||||
|
||||
@@ -127,30 +127,6 @@ void VulkanStateTracker::setRenderPass(vk::RenderPass renderPass, uint32_t subpa
|
||||
// recorders are ever removed.
|
||||
m_viewportDirty = true;
|
||||
m_scissorDirty = true;
|
||||
|
||||
m_screenScaleX = 1.0f;
|
||||
m_screenScaleY = 1.0f;
|
||||
}
|
||||
|
||||
void VulkanStateTracker::setScreenScale(float x, float y)
|
||||
{
|
||||
m_screenScaleX = x;
|
||||
m_screenScaleY = y;
|
||||
m_viewportDirty = true;
|
||||
m_scissorDirty = true;
|
||||
}
|
||||
|
||||
vk::Rect2D VulkanStateTracker::screenToFramebuffer(const vk::Rect2D& rect) const
|
||||
{
|
||||
if (m_screenScaleX == 1.0f && m_screenScaleY == 1.0f) {
|
||||
return rect;
|
||||
}
|
||||
// Scale the edges, so that adjacent rects stay adjacent
|
||||
const auto x0 = static_cast<int32_t>(std::lround(rect.offset.x * m_screenScaleX));
|
||||
const auto y0 = static_cast<int32_t>(std::lround(rect.offset.y * m_screenScaleY));
|
||||
const auto x1 = static_cast<int32_t>(std::lround((rect.offset.x + static_cast<float>(rect.extent.width)) * m_screenScaleX));
|
||||
const auto y1 = static_cast<int32_t>(std::lround((rect.offset.y + static_cast<float>(rect.extent.height)) * m_screenScaleY));
|
||||
return {{x0, y0}, {static_cast<uint32_t>(std::max(x1 - x0, 0)), static_cast<uint32_t>(std::max(y1 - y0, 0))}};
|
||||
}
|
||||
|
||||
void VulkanStateTracker::invalidateExternalBindings()
|
||||
@@ -189,24 +165,8 @@ vk::Rect2D VulkanStateTracker::clampToRenderArea(const vk::Rect2D& rect) const
|
||||
return result;
|
||||
}
|
||||
|
||||
void VulkanStateTracker::setScreenViewport(float x, float y, float width, float height)
|
||||
{
|
||||
setViewport(x, y, width, height);
|
||||
m_viewportInScreenUnits = true;
|
||||
m_viewportDirty = true;
|
||||
if (!m_scissorEnabled) {
|
||||
m_scissorDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanStateTracker::setViewport(float x, float y, float width, float height, float minDepth, float maxDepth)
|
||||
{
|
||||
if (m_viewportInScreenUnits) {
|
||||
m_viewportInScreenUnits = false;
|
||||
m_viewportDirty = true;
|
||||
m_scissorDirty = true;
|
||||
}
|
||||
|
||||
if (m_viewport.x != x || m_viewport.y != y ||
|
||||
m_viewport.width != width || m_viewport.height != height ||
|
||||
m_viewport.minDepth != minDepth || m_viewport.maxDepth != maxDepth) {
|
||||
@@ -227,14 +187,6 @@ void VulkanStateTracker::setViewport(float x, float y, float width, float height
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanStateTracker::setUpscalerSceneState(float jitterX, float jitterY, float lodBias)
|
||||
{
|
||||
m_upscalerJitterX = jitterX;
|
||||
m_upscalerJitterY = jitterY;
|
||||
m_upscalerLodBias = lodBias;
|
||||
m_viewportDirty = true;
|
||||
}
|
||||
|
||||
void VulkanStateTracker::setScissor(int32_t x, int32_t y, uint32_t width, uint32_t height)
|
||||
{
|
||||
if (m_scissor.offset.x != x || m_scissor.offset.y != y ||
|
||||
@@ -361,42 +313,28 @@ void VulkanStateTracker::applyDynamicState()
|
||||
{
|
||||
Assertion(m_cmdBuffer, "applyDynamicState called without active command buffer!");
|
||||
|
||||
vk::Viewport viewport = m_viewport;
|
||||
if (m_viewportInScreenUnits) {
|
||||
viewport.x *= m_screenScaleX;
|
||||
viewport.y *= m_screenScaleY;
|
||||
viewport.width *= m_screenScaleX;
|
||||
viewport.height *= m_screenScaleY;
|
||||
}
|
||||
|
||||
if (m_viewportDirty) {
|
||||
vk::Viewport jittered = viewport;
|
||||
if (isUpscaledScenePass()) {
|
||||
jittered.x += m_upscalerJitterX;
|
||||
jittered.y += m_upscalerJitterY;
|
||||
}
|
||||
m_cmdBuffer.setViewport(0, 1, &jittered);
|
||||
m_cmdBuffer.setViewport(0, 1, &m_viewport);
|
||||
m_viewportDirty = false;
|
||||
}
|
||||
|
||||
if (m_scissorDirty) {
|
||||
if (m_scissorEnabled) {
|
||||
const vk::Rect2D scissor = screenToFramebuffer(m_scissor);
|
||||
m_cmdBuffer.setScissor(0, 1, &scissor);
|
||||
m_cmdBuffer.setScissor(0, 1, &m_scissor);
|
||||
} else {
|
||||
// Set scissor to full viewport when disabled.
|
||||
// Handle negative viewport height (VK_KHR_maintenance1 Y-flip):
|
||||
// when height < 0, the viewport covers [y+height, y] in framebuffer Y.
|
||||
vk::Rect2D fullScissor;
|
||||
float vy = viewport.y;
|
||||
float vh = viewport.height;
|
||||
float vy = m_viewport.y;
|
||||
float vh = m_viewport.height;
|
||||
if (vh < 0.0f) {
|
||||
vy = vy + vh;
|
||||
vh = -vh;
|
||||
}
|
||||
fullScissor.offset.x = static_cast<int32_t>(viewport.x);
|
||||
fullScissor.offset.x = static_cast<int32_t>(m_viewport.x);
|
||||
fullScissor.offset.y = static_cast<int32_t>(vy);
|
||||
fullScissor.extent.width = static_cast<uint32_t>(viewport.width);
|
||||
fullScissor.extent.width = static_cast<uint32_t>(m_viewport.width);
|
||||
fullScissor.extent.height = static_cast<uint32_t>(vh);
|
||||
m_cmdBuffer.setScissor(0, 1, &fullScissor);
|
||||
}
|
||||
@@ -461,7 +399,7 @@ void vulkan_set_viewport(int x, int y, int width, int height)
|
||||
if (gr_screen.rendering_to_texture == -1) {
|
||||
// Screen rendering: use negative viewport height for OpenGL-compatible Y-up NDC
|
||||
// (VK_KHR_maintenance1, core since Vulkan 1.1)
|
||||
stateTracker->setScreenViewport(
|
||||
stateTracker->setViewport(
|
||||
static_cast<float>(x),
|
||||
static_cast<float>(gr_screen.max_h - y),
|
||||
static_cast<float>(width),
|
||||
|
||||
@@ -69,19 +69,6 @@ class VulkanStateTracker {
|
||||
*/
|
||||
void setViewport(float x, float y, float width, float height, float minDepth = 0.0f, float maxDepth = 1.0f);
|
||||
|
||||
// In gr_screen units, scaled by the screen scale
|
||||
void setScreenViewport(float x, float y, float width, float height);
|
||||
|
||||
// Framebuffer pixels per gr_screen unit for engine-issued viewports, scissors and clears. Above 1 only in the
|
||||
// composition pass while FSR upscales; every render pass begin resets it to 1.
|
||||
void setScreenScale(float x, float y);
|
||||
vk::Rect2D screenToFramebuffer(const vk::Rect2D& rect) const;
|
||||
|
||||
// FSR's subpixel jitter and texture LOD bias, applied only in the scene's own passes. Jittering the viewport rather
|
||||
// than the projection also moves CPU-projected backgrounds, and leaves the matrices motion vectors and the HUD use.
|
||||
void setUpscalerSceneState(float jitterX, float jitterY, float lodBias);
|
||||
float getMaterialLodBias() const { return isUpscaledScenePass() ? m_upscalerLodBias : 0.0f; }
|
||||
|
||||
/**
|
||||
* @brief Set scissor rectangle (dynamic state)
|
||||
*/
|
||||
@@ -336,17 +323,6 @@ class VulkanStateTracker {
|
||||
static_cast<size_t>(DescriptorSetIndex::Count)>
|
||||
m_boundDynamicOffsets{};
|
||||
|
||||
// Only the scene's passes have the motion vector location the upscaler requires.
|
||||
bool isUpscaledScenePass() const { return m_colorAttachmentCount > PipelineConfig::VELOCITY_ATTACHMENT; }
|
||||
|
||||
float m_screenScaleX = 1.0f;
|
||||
float m_screenScaleY = 1.0f;
|
||||
bool m_viewportInScreenUnits = false;
|
||||
|
||||
float m_upscalerJitterX = 0.0f;
|
||||
float m_upscalerJitterY = 0.0f;
|
||||
float m_upscalerLodBias = 0.0f;
|
||||
|
||||
// Dynamic state
|
||||
vk::Viewport m_viewport;
|
||||
vk::Rect2D m_scissor;
|
||||
|
||||
@@ -195,7 +195,6 @@ bool VulkanTextureManager::init(vk::Device device, vk::PhysicalDevice physicalDe
|
||||
auto properties = physicalDevice.getProperties();
|
||||
m_maxTextureSize = properties.limits.maxImageDimension2D;
|
||||
m_maxAnisotropy = properties.limits.maxSamplerAnisotropy;
|
||||
m_maxLodBias = properties.limits.maxSamplerLodBias;
|
||||
|
||||
nprintf(("vulkan", "Vulkan Texture Manager initialized\n"));
|
||||
nprintf(("vulkan", " Max texture size: %u\n", m_maxTextureSize));
|
||||
@@ -2161,13 +2160,8 @@ void VulkanTextureManager::decodeReadbackBuffer(const void* mapped, vk::Format f
|
||||
vk::Sampler VulkanTextureManager::getSampler(vk::Filter magFilter, vk::Filter minFilter,
|
||||
vk::SamplerAddressMode addressMode,
|
||||
bool enableAnisotropy, float maxAnisotropy,
|
||||
bool enableMipmaps, float negativeLodBias)
|
||||
bool enableMipmaps)
|
||||
{
|
||||
// The bias is quantized to the eighths the key can hold, so that the key always matches the sampler it caches
|
||||
const int maxLodBiasEighths = std::min(static_cast<int>(m_maxLodBias * 8.0f), 63);
|
||||
const int lodBiasEighths = std::clamp(static_cast<int>(std::lround(negativeLodBias * -8.0f)), 0, maxLodBiasEighths);
|
||||
const float lodBias = static_cast<float>(lodBiasEighths) / -8.0f;
|
||||
|
||||
// Create a key from sampler state
|
||||
uint64_t key = 0;
|
||||
key |= static_cast<uint64_t>(magFilter) << 0;
|
||||
@@ -2175,7 +2169,6 @@ vk::Sampler VulkanTextureManager::getSampler(vk::Filter magFilter, vk::Filter mi
|
||||
key |= static_cast<uint64_t>(addressMode) << 8;
|
||||
key |= static_cast<uint64_t>(enableAnisotropy) << 16;
|
||||
key |= static_cast<uint64_t>(enableMipmaps) << 17;
|
||||
key |= static_cast<uint64_t>(lodBiasEighths & 0x3F) << 18; // 6 bits, in eighths
|
||||
key |= static_cast<uint64_t>(maxAnisotropy * 10) << 24;
|
||||
|
||||
auto it = m_samplerCache.find(key);
|
||||
@@ -2197,7 +2190,7 @@ vk::Sampler VulkanTextureManager::getSampler(vk::Filter magFilter, vk::Filter mi
|
||||
samplerInfo.compareEnable = false;
|
||||
samplerInfo.compareOp = vk::CompareOp::eAlways;
|
||||
samplerInfo.mipmapMode = enableMipmaps ? vk::SamplerMipmapMode::eLinear : vk::SamplerMipmapMode::eNearest;
|
||||
samplerInfo.mipLodBias = lodBias;
|
||||
samplerInfo.mipLodBias = 0.0f;
|
||||
samplerInfo.minLod = 0.0f;
|
||||
samplerInfo.maxLod = enableMipmaps ? VK_LOD_CLAMP_NONE : 0.0f;
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ public:
|
||||
vk::Sampler getSampler(vk::Filter magFilter, vk::Filter minFilter,
|
||||
vk::SamplerAddressMode addressMode,
|
||||
bool enableAnisotropy, float maxAnisotropy,
|
||||
bool enableMipmaps, float negativeLodBias = 0.0f);
|
||||
bool enableMipmaps);
|
||||
|
||||
/**
|
||||
* @brief Get a ready-to-use DescriptorImageInfo for the 2D fallback texture
|
||||
@@ -420,7 +420,6 @@ private:
|
||||
// Device limits
|
||||
uint32_t m_maxTextureSize = 4096;
|
||||
float m_maxAnisotropy = 1.0f;
|
||||
float m_maxLodBias = 0.0f;
|
||||
|
||||
// Current render target state
|
||||
int m_currentRenderTarget = -1;
|
||||
|
||||
@@ -1,443 +0,0 @@
|
||||
#include "graphics/vulkan/VulkanUpscaler.h"
|
||||
|
||||
#include "globalincs/pstypes.h"
|
||||
#include "graphics/2d.h"
|
||||
#include "graphics/matrix.h"
|
||||
#include "graphics/vulkan/VulkanBarrier.h"
|
||||
#include "graphics/vulkan/VulkanMemory.h"
|
||||
#include "graphics/vulkan/VulkanPostProcessing.h"
|
||||
#include "render/3d.h"
|
||||
|
||||
#ifdef WITH_FFX
|
||||
#include <ffx_portable.h>
|
||||
#endif
|
||||
|
||||
#include <optional>
|
||||
|
||||
extern float flRealframetime;
|
||||
|
||||
namespace graphics::vulkan {
|
||||
|
||||
#ifdef WITH_FFX
|
||||
|
||||
namespace {
|
||||
|
||||
// vkCreateDevice reads through pointers into this, so it has to outlive the caller's scope.
|
||||
ffxPortableVkRequirements Device_requirements;
|
||||
bool Upscaler_available = false;
|
||||
vk::PhysicalDevice Upscaler_gpu;
|
||||
|
||||
// The mode and version FSR failed with. Choosing anything else gives it another try.
|
||||
struct failed_setting {
|
||||
FsrMode mode;
|
||||
int majorVersion;
|
||||
};
|
||||
std::optional<failed_setting> Failed_setting;
|
||||
|
||||
bool failed_for_current_setting()
|
||||
{
|
||||
if (Failed_setting && (Failed_setting->mode != Gr_fsr_mode || Failed_setting->majorVersion != Gr_fsr_major_version)) {
|
||||
Failed_setting.reset();
|
||||
}
|
||||
return Failed_setting.has_value();
|
||||
}
|
||||
|
||||
void mark_failed()
|
||||
{
|
||||
Failed_setting = failed_setting{Gr_fsr_mode, Gr_fsr_major_version};
|
||||
}
|
||||
|
||||
struct fsr_version {
|
||||
uint64_t id = 0;
|
||||
SCP_string name;
|
||||
};
|
||||
|
||||
// AMD lists the versions this device can run, the one it selects by default first.
|
||||
SCP_vector<fsr_version> query_fsr_versions(vk::Device device)
|
||||
{
|
||||
VkDeviceContext context{static_cast<VkDevice>(device), static_cast<VkPhysicalDevice>(Upscaler_gpu),
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.vkGetDeviceProcAddr};
|
||||
|
||||
uint64_t count = 0;
|
||||
ffxQueryDescGetVersions query{};
|
||||
query.header.type = FFX_API_QUERY_DESC_TYPE_GET_VERSIONS;
|
||||
query.createDescType = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE;
|
||||
query.device = &context;
|
||||
query.outputCount = &count;
|
||||
if (ffxQuery(nullptr, &query.header) != FFX_API_RETURN_OK || count == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto n = static_cast<size_t>(count);
|
||||
SCP_vector<uint64_t> ids(n);
|
||||
SCP_vector<const char*> names(n);
|
||||
query.versionIds = ids.data();
|
||||
query.versionNames = names.data();
|
||||
if (ffxQuery(nullptr, &query.header) != FFX_API_RETURN_OK) {
|
||||
return {};
|
||||
}
|
||||
|
||||
SCP_vector<fsr_version> versions;
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
versions.push_back({ids[i], names[i] ? names[i] : ""});
|
||||
}
|
||||
return versions;
|
||||
}
|
||||
|
||||
int major_version(const SCP_string& name)
|
||||
{
|
||||
return std::atoi(name.c_str());
|
||||
}
|
||||
|
||||
bool is_offered(int major)
|
||||
{
|
||||
return major == 4 || major == 3;
|
||||
}
|
||||
|
||||
// The version the user chose
|
||||
const fsr_version* find_chosen_version(const SCP_vector<fsr_version>& versions)
|
||||
{
|
||||
for (const auto& version : versions) {
|
||||
if (major_version(version.name) == Gr_fsr_major_version) {
|
||||
return &version;
|
||||
}
|
||||
}
|
||||
mprintf(("Vulkan: FSR %d isn't available, leaving the version to AMD's selection\n", Gr_fsr_major_version));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void log_ffx_message(uint32_t type, const wchar_t* message)
|
||||
{
|
||||
mprintf(("FSR %s: %ls\n", type == FFX_API_MESSAGE_TYPE_ERROR ? "error" : "warning", message));
|
||||
}
|
||||
|
||||
FfxApiResource ffx_image(const RenderTarget& target, FfxApiResourceState state, uint32_t usage = FFX_API_RESOURCE_USAGE_READ_ONLY)
|
||||
{
|
||||
FfxApiResourceDescription description{};
|
||||
description.type = FFX_API_RESOURCE_TYPE_TEXTURE2D;
|
||||
description.format = ffxApiGetSurfaceFormatVK(static_cast<VkFormat>(target.format));
|
||||
description.width = target.width;
|
||||
description.height = target.height;
|
||||
description.depth = 1;
|
||||
description.mipCount = 1;
|
||||
description.usage = usage;
|
||||
return ffxApiGetResourceVK(static_cast<VkImage>(target.image), description, state);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void vulkan_upscaler_prepare_device(vk::Instance instance,
|
||||
vk::PhysicalDevice gpu,
|
||||
uint32_t graphicsQueueFamily,
|
||||
const vk::PhysicalDeviceFeatures& supportedFeatures,
|
||||
vk::DeviceCreateInfo& createInfo)
|
||||
{
|
||||
Upscaler_available = false;
|
||||
|
||||
if (!ffxPortableCanLoad()) {
|
||||
mprintf(("Vulkan: FSR unavailable: %s\n", ffxPortableLoadError()));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto queueFamilies = gpu.getQueueFamilyProperties();
|
||||
if (graphicsQueueFamily >= queueFamilies.size() ||
|
||||
!(queueFamilies[graphicsQueueFamily].queueFlags & vk::QueueFlagBits::eCompute)) {
|
||||
mprintf(("Vulkan: FSR unavailable: the graphics queue has no compute support\n"));
|
||||
return;
|
||||
}
|
||||
if (!supportedFeatures.independentBlend) {
|
||||
mprintf(("Vulkan: FSR unavailable: independentBlend is not supported\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
ffxPortableVkGetDeviceRequirements(static_cast<VkInstance>(instance),
|
||||
static_cast<VkPhysicalDevice>(gpu),
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.vkGetInstanceProcAddr,
|
||||
&Device_requirements);
|
||||
|
||||
if (VulkanApiVersion < Device_requirements.minApiVersion) {
|
||||
mprintf(("Vulkan: FSR unavailable: needs a newer Vulkan API version than the renderer uses\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ffxPortableVkApplyRequirements(&Device_requirements, reinterpret_cast<VkDeviceCreateInfo*>(&createInfo))) {
|
||||
mprintf(("Vulkan: FSR unavailable: too many device extensions\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
mprintf(("Vulkan: FSR device requirements applied (FP8 capable: %s, INT8 capable: %s)\n",
|
||||
Device_requirements.fp8Capable ? "yes" : "no",
|
||||
Device_requirements.int8Capable ? "yes" : "no"));
|
||||
Upscaler_gpu = gpu;
|
||||
Upscaler_available = true;
|
||||
}
|
||||
|
||||
void vulkan_upscaler_device_created(vk::Device device)
|
||||
{
|
||||
if (!Upscaler_available) {
|
||||
return;
|
||||
}
|
||||
|
||||
Gr_fsr_max_output_size = static_cast<int>(Upscaler_gpu.getProperties().limits.maxImageDimension2D);
|
||||
Gr_fsr_versions.clear();
|
||||
const auto versions = query_fsr_versions(device);
|
||||
for (const auto& version : versions) {
|
||||
mprintf(("Vulkan: FSR version available: %s\n", version.name.c_str()));
|
||||
if (is_offered(major_version(version.name))) {
|
||||
Gr_fsr_versions.push_back(version.name);
|
||||
}
|
||||
}
|
||||
// FSR 2 is only the fallback for hardware where neither of the newer versions is available
|
||||
if (Gr_fsr_versions.empty()) {
|
||||
for (const auto& version : versions) {
|
||||
if (major_version(version.name) == 2) {
|
||||
Gr_fsr_versions.push_back(version.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::sort(Gr_fsr_versions.begin(), Gr_fsr_versions.end(),
|
||||
[](const SCP_string& a, const SCP_string& b) { return major_version(a) > major_version(b); });
|
||||
}
|
||||
|
||||
bool vulkan_upscaler_available()
|
||||
{
|
||||
return Upscaler_available;
|
||||
}
|
||||
|
||||
bool vulkan_upscaler_usable()
|
||||
{
|
||||
return Upscaler_available && !failed_for_current_setting();
|
||||
}
|
||||
|
||||
bool VulkanUpscaler::wanted()
|
||||
{
|
||||
return vulkan_upscaler_usable() && Gr_fsr_mode != FsrMode::Off;
|
||||
}
|
||||
|
||||
bool VulkanUpscaler::createResources()
|
||||
{
|
||||
if (m_context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint32_t renderW = m_ctx->sceneExtent.width;
|
||||
const uint32_t renderH = m_ctx->sceneExtent.height;
|
||||
const uint32_t w = m_ctx->outputExtent.width;
|
||||
const uint32_t h = m_ctx->outputExtent.height;
|
||||
|
||||
m_output.format = HDR_COLOR_FORMAT;
|
||||
m_output.width = w;
|
||||
m_output.height = h;
|
||||
if (!m_ctx->createImage(w, h, HDR_COLOR_FORMAT,
|
||||
vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eColorAttachment,
|
||||
vk::ImageAspectFlagBits::eColor,
|
||||
m_output.image, m_output.view, m_output.allocation)) {
|
||||
mprintf(("Vulkan: FSR disabled: could not create its output image\n"));
|
||||
mark_failed();
|
||||
return false;
|
||||
}
|
||||
|
||||
ffxCreateBackendVKDesc backend{};
|
||||
backend.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_VK;
|
||||
backend.deviceContext.vkDevice = static_cast<VkDevice>(m_ctx->device);
|
||||
backend.deviceContext.vkPhysicalDevice = static_cast<VkPhysicalDevice>(Upscaler_gpu);
|
||||
backend.deviceContext.vkDeviceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetDeviceProcAddr;
|
||||
|
||||
ffxCreateContextDescUpscaleVersion version{};
|
||||
version.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE_VERSION;
|
||||
version.header.pNext = &backend.header;
|
||||
version.version = FFX_UPSCALER_VERSION;
|
||||
|
||||
ffxCreateContextDescUpscale desc{};
|
||||
desc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE;
|
||||
desc.header.pNext = &version.header;
|
||||
desc.flags = FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE | FFX_UPSCALE_ENABLE_AUTO_EXPOSURE;
|
||||
desc.maxRenderSize = {renderW, renderH};
|
||||
desc.maxUpscaleSize = {w, h};
|
||||
desc.fpMessage = log_ffx_message;
|
||||
|
||||
ffxOverrideVersion chosenVersion{};
|
||||
const auto versions = query_fsr_versions(m_ctx->device);
|
||||
if (const auto* chosen = find_chosen_version(versions)) {
|
||||
chosenVersion.header.type = FFX_API_DESC_TYPE_OVERRIDE_VERSION;
|
||||
chosenVersion.header.pNext = desc.header.pNext;
|
||||
chosenVersion.versionId = chosen->id;
|
||||
desc.header.pNext = &chosenVersion.header;
|
||||
}
|
||||
m_contextMajorVersion = Gr_fsr_major_version;
|
||||
|
||||
const auto result = ffxCreateContext(&m_context, &desc.header, nullptr);
|
||||
if (result != FFX_API_RETURN_OK) {
|
||||
mprintf(("Vulkan: FSR disabled: creating its context failed with code %u\n", result));
|
||||
m_context = nullptr;
|
||||
m_ctx->destroyTarget(m_output);
|
||||
mark_failed();
|
||||
return false;
|
||||
}
|
||||
|
||||
ffxQueryGetProviderVersion provider{};
|
||||
provider.header.type = FFX_API_QUERY_DESC_TYPE_GET_PROVIDER_VERSION;
|
||||
const bool versionKnown = ffxQuery(&m_context, &provider.header) == FFX_API_RETURN_OK && provider.versionName;
|
||||
mprintf(("Vulkan: FSR context created for %ux%u to %ux%u, loaded FSR version: %s\n", renderW, renderH, w, h,
|
||||
versionKnown ? provider.versionName : "unknown"));
|
||||
|
||||
ffxQueryDescUpscaleGetJitterPhaseCount phaseCount{};
|
||||
phaseCount.header.type = FFX_API_QUERY_DESC_TYPE_UPSCALE_GETJITTERPHASECOUNT;
|
||||
phaseCount.renderWidth = renderW;
|
||||
phaseCount.displayWidth = w;
|
||||
phaseCount.pOutPhaseCount = &m_jitterPhaseCount;
|
||||
if (ffxQuery(&m_context, &phaseCount.header) != FFX_API_RETURN_OK || m_jitterPhaseCount < 1) {
|
||||
m_jitterPhaseCount = 1;
|
||||
}
|
||||
m_jitterIndex = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanUpscaler::destroyTargets()
|
||||
{
|
||||
if (!m_ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_context) {
|
||||
ffxDestroyContext(&m_context, nullptr);
|
||||
m_context = nullptr;
|
||||
}
|
||||
m_ctx->destroyTarget(m_output);
|
||||
}
|
||||
|
||||
void VulkanUpscaler::beginFrame(float& imageOffsetX, float& imageOffsetY, float& lodBias)
|
||||
{
|
||||
m_jitterIndex = (m_jitterIndex + 1) % m_jitterPhaseCount;
|
||||
|
||||
ffxQueryDescUpscaleGetJitterOffset query{};
|
||||
query.header.type = FFX_API_QUERY_DESC_TYPE_UPSCALE_GETJITTEROFFSET;
|
||||
query.index = m_jitterIndex;
|
||||
query.phaseCount = m_jitterPhaseCount;
|
||||
query.pOutX = &m_jitterX;
|
||||
query.pOutY = &m_jitterY;
|
||||
if (ffxQuery(&m_context, &query.header) != FFX_API_RETURN_OK) {
|
||||
m_jitterX = 0.0f;
|
||||
m_jitterY = 0.0f;
|
||||
}
|
||||
|
||||
// FSR takes a pixel to show the content at its position minus the jitter, so the image moves by the jitter, in
|
||||
// image space with Y down.
|
||||
imageOffsetX = m_jitterX;
|
||||
imageOffsetY = m_jitterY;
|
||||
lodBias = std::log2(static_cast<float>(m_ctx->sceneExtent.width) / static_cast<float>(m_output.width)) - 1.0f;
|
||||
}
|
||||
|
||||
bool VulkanUpscaler::dispatch(vk::CommandBuffer cmd, const RenderTarget& color, const RenderTarget& depth, const RenderTarget& motion)
|
||||
{
|
||||
GR_DEBUG_SCOPE("FSR");
|
||||
|
||||
cmdMemoryBarrier(cmd,
|
||||
vk::PipelineStageFlagBits2::eColorAttachmentOutput,
|
||||
vk::AccessFlagBits2::eColorAttachmentWrite,
|
||||
vk::PipelineStageFlagBits2::eComputeShader,
|
||||
vk::AccessFlagBits2::eShaderRead);
|
||||
|
||||
ImageBarrier2 barrier;
|
||||
barrier.image = m_output.image;
|
||||
barrier.levelCount = 1;
|
||||
barrier.layerCount = 1;
|
||||
barrier.oldLayout = vk::ImageLayout::eUndefined;
|
||||
barrier.newLayout = vk::ImageLayout::eGeneral;
|
||||
// Last frame's bloom composited into the output, and tonemapping read it
|
||||
barrier.srcStage = vk::PipelineStageFlagBits2::eColorAttachmentOutput | vk::PipelineStageFlagBits2::eFragmentShader;
|
||||
barrier.srcAccess = vk::AccessFlagBits2::eColorAttachmentWrite;
|
||||
barrier.dstStage = vk::PipelineStageFlagBits2::eComputeShader;
|
||||
barrier.dstAccess = vk::AccessFlagBits2::eShaderWrite;
|
||||
cmdImageBarrier(cmd, barrier);
|
||||
|
||||
ffxDispatchDescUpscale desc{};
|
||||
desc.header.type = FFX_API_DISPATCH_DESC_TYPE_UPSCALE;
|
||||
desc.commandList = static_cast<VkCommandBuffer>(cmd);
|
||||
desc.color = ffx_image(color, FFX_API_RESOURCE_STATE_COMPUTE_READ);
|
||||
desc.depth = ffx_image(depth, FFX_API_RESOURCE_STATE_COMPUTE_READ);
|
||||
desc.motionVectors = ffx_image(motion, FFX_API_RESOURCE_STATE_COMPUTE_READ);
|
||||
desc.output = ffx_image(m_output, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS, FFX_API_RESOURCE_USAGE_UAV);
|
||||
desc.jitterOffset = {m_jitterX, m_jitterY};
|
||||
desc.renderSize = {color.width, color.height};
|
||||
desc.upscaleSize = {m_output.width, m_output.height};
|
||||
// Motion vectors are in UV units
|
||||
desc.motionVectorScale = {static_cast<float>(color.width), static_cast<float>(color.height)};
|
||||
desc.frameTimeDelta = flRealframetime * 1000.0f;
|
||||
desc.preExposure = 1.0f;
|
||||
desc.reset = gr_consume_temporal_reset();
|
||||
// The motion vector pass puts all depth on the cockpit's projection
|
||||
desc.cameraNear = Min_draw_distance_cockpit;
|
||||
desc.cameraFar = Max_draw_distance;
|
||||
desc.cameraFovAngleVertical = std::holds_alternative<float>(Proj_fov) ? std::get<float>(Proj_fov) : 1.0f;
|
||||
desc.viewSpaceToMetersFactor = 1.0f;
|
||||
|
||||
const auto result = ffxDispatch(&m_context, &desc.header);
|
||||
const bool dispatched = result == FFX_API_RETURN_OK;
|
||||
if (!dispatched) {
|
||||
mprintf(("Vulkan: FSR disabled: dispatch failed with code %u\n", result));
|
||||
mark_failed();
|
||||
}
|
||||
|
||||
barrier.oldLayout = vk::ImageLayout::eGeneral;
|
||||
barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
barrier.srcStage = vk::PipelineStageFlagBits2::eComputeShader;
|
||||
barrier.srcAccess = vk::AccessFlagBits2::eShaderWrite;
|
||||
barrier.dstStage = vk::PipelineStageFlagBits2::eFragmentShader;
|
||||
barrier.dstAccess = vk::AccessFlagBits2::eShaderSampledRead;
|
||||
cmdImageBarrier(cmd, barrier);
|
||||
|
||||
return dispatched;
|
||||
}
|
||||
|
||||
#else // WITH_FFX
|
||||
|
||||
void vulkan_upscaler_prepare_device(vk::Instance /*instance*/,
|
||||
vk::PhysicalDevice /*gpu*/,
|
||||
uint32_t /*graphicsQueueFamily*/,
|
||||
const vk::PhysicalDeviceFeatures& /*supportedFeatures*/,
|
||||
vk::DeviceCreateInfo& /*createInfo*/)
|
||||
{
|
||||
}
|
||||
|
||||
void vulkan_upscaler_device_created(vk::Device /*device*/)
|
||||
{
|
||||
}
|
||||
|
||||
bool vulkan_upscaler_available()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool vulkan_upscaler_usable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VulkanUpscaler::wanted()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VulkanUpscaler::createResources()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void VulkanUpscaler::destroyTargets()
|
||||
{
|
||||
}
|
||||
|
||||
void VulkanUpscaler::beginFrame(float& /*imageOffsetX*/, float& /*imageOffsetY*/, float& /*lodBias*/)
|
||||
{
|
||||
}
|
||||
|
||||
bool VulkanUpscaler::dispatch(vk::CommandBuffer /*cmd*/, const RenderTarget& /*color*/, const RenderTarget& /*depth*/, const RenderTarget& /*motion*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif // WITH_FFX
|
||||
|
||||
} // namespace graphics::vulkan
|
||||
@@ -1,26 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
namespace graphics::vulkan {
|
||||
|
||||
/**
|
||||
* @brief Adds what FSR needs to the device about to be created, if FSR can run on it
|
||||
*
|
||||
* Must run after the create info's pNext chain is final, and the create info must not outlive the process-lifetime
|
||||
* storage this keeps for it. Failure only means FSR stays unavailable; the renderer carries on without it.
|
||||
*/
|
||||
void vulkan_upscaler_prepare_device(vk::Instance instance,
|
||||
vk::PhysicalDevice gpu,
|
||||
uint32_t graphicsQueueFamily,
|
||||
const vk::PhysicalDeviceFeatures& supportedFeatures,
|
||||
vk::DeviceCreateInfo& createInfo);
|
||||
|
||||
// Looks up which FSR version this device gets, for display.
|
||||
void vulkan_upscaler_device_created(vk::Device device);
|
||||
|
||||
bool vulkan_upscaler_available();
|
||||
// Available, and hasn't failed with the currently chosen mode and version
|
||||
bool vulkan_upscaler_usable();
|
||||
|
||||
} // namespace graphics::vulkan
|
||||
@@ -32,8 +32,6 @@ const VertexFormatMapping VERTEX_FORMAT_MAPPINGS[] = {
|
||||
|
||||
// Matrix format (mat4 = 4 vec4s, uses locations 8-11)
|
||||
{ vertex_format_data::MATRIX4, vk::Format::eR32G32B32A32Sfloat, VATTRIB_MODEL_MATRIX, 16, 64 },
|
||||
|
||||
{ vertex_format_data::MOTION4, vk::Format::eR32G32B32A32Sfloat, VATTRIB_EFFECT_MOTION, 4, 16 },
|
||||
};
|
||||
|
||||
const size_t VERTEX_FORMAT_MAPPINGS_COUNT = sizeof(VERTEX_FORMAT_MAPPINGS) / sizeof(VERTEX_FORMAT_MAPPINGS[0]);
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include "graphics/post_processing.h"
|
||||
#include "graphics/grinternal.h"
|
||||
#include "graphics/shadows.h"
|
||||
#include "graphics/vulkan/VulkanUpscaler.h"
|
||||
#include "lighting/lighting.h"
|
||||
#include "pngutils/pngutils.h"
|
||||
|
||||
@@ -76,6 +75,32 @@ void vulkan_model_unloaded(int pm_id)
|
||||
}
|
||||
}
|
||||
|
||||
void vulkan_build_shadow_tlas()
|
||||
{
|
||||
if (auto* rt = getRaytracingManager()) {
|
||||
// vkCmdBuildAccelerationStructuresKHR (and the memory barrier that follows
|
||||
// it in buildTlas()) must be recorded outside any render pass instance.
|
||||
// This is called once per frame, before the shadow map render pass begins,
|
||||
// while the previous render pass (G-buffer/scene) is typically still
|
||||
// active -- end it here. vulkan_shadow_map_start()'s first_pass branch
|
||||
// skips its own endRenderPass() call when it finds none active.
|
||||
auto* stateTracker = getStateTracker();
|
||||
if (stateTracker->getCurrentRenderPass()) {
|
||||
stateTracker->getCommandBuffer().endRenderPass();
|
||||
stateTracker->setRenderPass(vk::RenderPass());
|
||||
}
|
||||
|
||||
rt->buildTlas();
|
||||
// Refresh the Global set's live TLAS fallback so every writeSet(Global)
|
||||
// this frame picks up the freshly built TLAS automatically -- see
|
||||
// DescriptorFallbacks::shadowTlas.
|
||||
getDescriptorManager()->setCurrentShadowTlas(rt->getTlasForShaderBinding());
|
||||
// The memoized per-frame Global set (VulkanDrawManager) must rebuild so the
|
||||
// new TLAS reaches subsequent draws' Set 0 instead of a cached stale one.
|
||||
getDrawManager()->invalidateGlobalSet();
|
||||
}
|
||||
}
|
||||
|
||||
bool vulkan_is_capable(gr_capability capability)
|
||||
{
|
||||
switch (capability) {
|
||||
@@ -137,12 +162,6 @@ bool vulkan_is_capable(gr_capability capability)
|
||||
return false;
|
||||
case gr_capability::CAPABILITY_RAYTRACED_SHADOWS:
|
||||
return getRendererInstance()->supportsRaytracedShadows();
|
||||
case gr_capability::CAPABILITY_SHADOW_CONTACT_HARDENING:
|
||||
// Vulkan samplers are independent of images, so a second (non-compare) sampler on
|
||||
// the same shadow map view is always available -- no capability gap here.
|
||||
return true;
|
||||
case gr_capability::CAPABILITY_FSR:
|
||||
return vulkan_upscaler_available();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -233,14 +252,7 @@ void vulkan_imgui_render_draw_data()
|
||||
{
|
||||
auto* renderer = getRendererInstance();
|
||||
if (renderer) {
|
||||
// The composition image is larger than ImGui's display while FSR upscales
|
||||
auto* drawData = ImGui::GetDrawData();
|
||||
const auto extent = renderer->getCompositionExtent();
|
||||
if (drawData && drawData->DisplaySize.x > 0.0f && drawData->DisplaySize.y > 0.0f) {
|
||||
drawData->FramebufferScale = ImVec2(static_cast<float>(extent.width) / drawData->DisplaySize.x,
|
||||
static_cast<float>(extent.height) / drawData->DisplaySize.y);
|
||||
}
|
||||
ImGui_ImplVulkan_RenderDrawData(drawData, renderer->getVkCurrentCommandBuffer());
|
||||
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), renderer->getVkCurrentCommandBuffer());
|
||||
|
||||
// ImGui just bound its own pipeline/descriptor set directly on the command buffer,
|
||||
// inside the same (already-active) render pass FSO's own draws share -- it can't begin
|
||||
@@ -304,24 +316,6 @@ int vulkan_save_screen()
|
||||
return -1;
|
||||
}
|
||||
|
||||
// While FSR upscales, the frame is larger than gr_screen, but restoring draws it 1:1 in gr_screen pixels
|
||||
const auto screen_w = static_cast<uint32_t>(gr_screen.max_w);
|
||||
const auto screen_h = static_cast<uint32_t>(gr_screen.max_h);
|
||||
if (w != screen_w || h != screen_h) {
|
||||
auto* scaled = static_cast<ubyte*>(vm_malloc(static_cast<int>(screen_w * screen_h * 4)));
|
||||
for (uint32_t y = 0; y < screen_h; ++y) {
|
||||
const uint32_t src_y = std::min((2 * y + 1) * h / (2 * screen_h), h - 1);
|
||||
for (uint32_t x = 0; x < screen_w; ++x) {
|
||||
const uint32_t src_x = std::min((2 * x + 1) * w / (2 * screen_w), w - 1);
|
||||
memcpy(&scaled[(y * screen_w + x) * 4], &pixels[(src_y * w + src_x) * 4], 4);
|
||||
}
|
||||
}
|
||||
vm_free(pixels);
|
||||
pixels = scaled;
|
||||
w = screen_w;
|
||||
h = screen_h;
|
||||
}
|
||||
|
||||
int bmpId = bm_create(32, static_cast<int>(w), static_cast<int>(h), pixels, 0);
|
||||
if (bmpId < 0) {
|
||||
vm_free(pixels);
|
||||
@@ -509,6 +503,7 @@ void init_function_pointers()
|
||||
|
||||
gr_screen.gf_model_loaded = vulkan_model_loaded;
|
||||
gr_screen.gf_model_unloaded = vulkan_model_unloaded;
|
||||
gr_screen.gf_build_shadow_tlas = vulkan_build_shadow_tlas;
|
||||
|
||||
gr_screen.gf_update_transform_buffer = vulkan_update_transform_buffer;
|
||||
gr_screen.gf_update_buffer_data = vulkan_update_buffer_data;
|
||||
@@ -606,30 +601,6 @@ void init_function_pointers()
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
bool vulkan_frame_tlas_pending()
|
||||
{
|
||||
auto* rt = getRaytracingManager();
|
||||
return rt != nullptr && rt->isEnabled() && shadows_rt_scene_needed() && !rt->isTlasBuiltThisFrame();
|
||||
}
|
||||
|
||||
void vulkan_build_frame_tlas()
|
||||
{
|
||||
auto* rt = getRaytracingManager();
|
||||
if (rt == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// vkCmdBuildAccelerationStructuresKHR and the barrier after it must be recorded outside a
|
||||
// render pass instance. The caller makes sure that no render pass is open.
|
||||
rt->buildTlas();
|
||||
// Refresh the live TLAS value of the Global set, so that each writeSet(Global) in this
|
||||
// frame gets the new TLAS. See DescriptorFallbacks::shadowTlas.
|
||||
getDescriptorManager()->setCurrentShadowTlas(rt->getTlasForShaderBinding());
|
||||
// The memoized Global set of VulkanDrawManager must rebuild, so that the next draws get
|
||||
// the new TLAS and not a stale cached set.
|
||||
getDrawManager()->invalidateGlobalSet();
|
||||
}
|
||||
|
||||
void initialize_function_pointers() {
|
||||
init_function_pointers();
|
||||
}
|
||||
|
||||
@@ -11,15 +11,6 @@ bool initialize(std::unique_ptr<os::GraphicsOperations>&& graphicsOps);
|
||||
|
||||
VulkanRenderer* getRendererInstance();
|
||||
|
||||
// True when this frame needs a ray-tracing TLAS (see shadows_rt_scene_needed()) and no build
|
||||
// for this frame occurred yet.
|
||||
bool vulkan_frame_tlas_pending();
|
||||
|
||||
// Builds the ray-tracing TLAS for this frame. vulkan_scene_texture_begin() calls this once per
|
||||
// frame, before the scene render pass starts. Call it only when no render pass is open, because
|
||||
// acceleration-structure builds are not permitted inside a render pass.
|
||||
void vulkan_build_frame_tlas();
|
||||
|
||||
void cleanup();
|
||||
|
||||
} // namespace graphics::vulkan
|
||||
@@ -599,6 +599,7 @@ void HudGaugeTargetBox::renderTargetSetup(vec3d *camera_eye, matrix *camera_orie
|
||||
g3_start_frame(1); // Turn on zbuffering
|
||||
hud_save_restore_camera_data(1);
|
||||
g3_set_view_matrix( camera_eye, camera_orient, zoom);
|
||||
model_set_detail_level(1); // use medium detail level
|
||||
|
||||
setClip(position[0] + Viewport_offsets[0], position[1] + Viewport_offsets[1], Viewport_w, Viewport_h);
|
||||
|
||||
|
||||
@@ -528,32 +528,6 @@ void LabUi::build_shadow_method_combobox()
|
||||
}
|
||||
}
|
||||
|
||||
static const char* rt_shadow_quality_settings[] = {
|
||||
"Low (directional lights only)",
|
||||
"High (also point/tube/cone lights)",
|
||||
};
|
||||
|
||||
void LabUi::build_rt_shadow_quality_combobox()
|
||||
{
|
||||
// Only meaningful when raytraced shadows are actually the active method.
|
||||
if (!shadows_raytracing_supported() || Shadow_render_method != ShadowRenderMethod::Raytraced) {
|
||||
return;
|
||||
}
|
||||
|
||||
with_Combo("Raytraced Shadow Quality", rt_shadow_quality_settings[static_cast<int>(Rt_shadow_quality)])
|
||||
{
|
||||
for (int n = 0; n < IM_ARRAYSIZE(rt_shadow_quality_settings); n++) {
|
||||
bool is_selected = static_cast<int>(Rt_shadow_quality) == n;
|
||||
|
||||
if (Selectable(rt_shadow_quality_settings[n], is_selected))
|
||||
LabRenderer::setRtShadowQuality(static_cast<RTShadowQuality>(n));
|
||||
|
||||
if (is_selected)
|
||||
SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LabUi::build_max_rt_shadow_lights_slider()
|
||||
{
|
||||
// Only meaningful when raytraced shadows are actually the active method.
|
||||
@@ -565,19 +539,6 @@ void LabUi::build_max_rt_shadow_lights_slider()
|
||||
if (SliderInt("Max Raytraced Shadow Lights", &count, 1, 8)) {
|
||||
LabRenderer::setMaxRtShadowLights(count);
|
||||
}
|
||||
|
||||
// The local-light cap is only consulted at High quality, so don't offer it at Low --
|
||||
// same as how the sun size slider only appears once its override is on.
|
||||
if (Rt_shadow_quality != RTShadowQuality::High) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 0 is a useful setting rather than a degenerate one: it turns local-light shadows off
|
||||
// without leaving High, which isolates what the directional lights are contributing.
|
||||
int local_count = Max_rt_shadow_local_lights;
|
||||
if (SliderInt("Max Raytraced Local Shadow Lights", &local_count, 0, 64)) {
|
||||
LabRenderer::setMaxRtShadowLocalLights(local_count);
|
||||
}
|
||||
}
|
||||
|
||||
void LabUi::build_rt_shadow_bias_sliders()
|
||||
@@ -598,84 +559,6 @@ void LabUi::build_rt_shadow_bias_sliders()
|
||||
}
|
||||
}
|
||||
|
||||
void LabUi::build_shadow_penumbra_sliders()
|
||||
{
|
||||
// The sample count is a raytracing cost knob, so it only appears when raytraced shadows
|
||||
// are actually the active method. The sun size below is deliberately not gated that way.
|
||||
if (shadows_raytracing_supported() && Shadow_render_method == ShadowRenderMethod::Raytraced) {
|
||||
// Starts at whatever Shadow_quality implies; moving it pins a session-only
|
||||
// override, so the slider stops tracking the quality tier from then on.
|
||||
int samples = shadows_rt_sample_count();
|
||||
if (SliderInt("Raytraced Shadow Samples", &samples, 1, 16)) {
|
||||
LabRenderer::setRtShadowSamples(samples);
|
||||
}
|
||||
}
|
||||
|
||||
// Contact hardening is a shadow-map-only technique (see pcssPenumbraRadius() in
|
||||
// shadows.sdr) -- raytraced shadows size their penumbra from traceShadowRayCone()'s
|
||||
// cone sampling instead, so this has no effect there and isn't worth showing.
|
||||
if (shadow_contact_hardening_supported() && Shadow_render_method == ShadowRenderMethod::ShadowMap) {
|
||||
bool contact_hardening = Shadow_contact_hardening_enabled;
|
||||
if (Checkbox("Shadow Contact Hardening", &contact_hardening)) {
|
||||
LabRenderer::setShadowContactHardening(contact_hardening);
|
||||
}
|
||||
}
|
||||
|
||||
// Overrides whatever size the suns would otherwise use -- their $SunAngularSize, or
|
||||
// the size measured from their bitmaps -- so penumbras can be tried out against any
|
||||
// background; the checkbox's default slider value is Sol's apparent diameter.
|
||||
//
|
||||
// Shown for both shadow methods, because this is the one parameter that drives softness
|
||||
// in both: it sizes the raytraced penumbra cone and scales the shadow map's filter width
|
||||
// (see shadow_smoothness_scale() in shadows.cpp). Sitting on the slider and flipping the
|
||||
// render method is the intended way to check the two against each other.
|
||||
//
|
||||
// It needs a mission background loaded to do anything: it overrides the size of drawn
|
||||
// suns, and with no background the lab lights the scene from common_setup_room_lights()
|
||||
// instead, which draws no suns for this to override.
|
||||
if (Shadow_quality == ShadowQuality::Disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool override_sun = Sun_angular_size_override >= 0.0f;
|
||||
if (Checkbox("Override Sun Angular Size", &override_sun)) {
|
||||
LabRenderer::setSunAngularSizeOverride(override_sun ? SUN_ANGULAR_SIZE_SOL : SUN_ANGULAR_SIZE_UNSPECIFIED);
|
||||
}
|
||||
if (override_sun) {
|
||||
float sun_size = Sun_angular_size_override;
|
||||
if (SliderFloat("Sun Angular Size (degrees)", &sun_size, 0.0f, 10.0f)) {
|
||||
LabRenderer::setSunAngularSizeOverride(sun_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LabUi::build_rtao_sliders()
|
||||
{
|
||||
// Unlike the RT shadow sliders, RTAO (raytraced ambient occlusion) does not depend on
|
||||
// the shadow method or the shadow quality. It only needs ray-query support.
|
||||
if (!rtao_supported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int samples = Rtao_samples;
|
||||
if (SliderInt("Ambient Occlusion Rays", &samples, 0, 16)) {
|
||||
LabRenderer::setRtaoSamples(samples);
|
||||
}
|
||||
|
||||
// Radius/strength are the mod-owned lighting-profile values ($RTAO Radius /
|
||||
// $RTAO Strength); these override the active profile for this session only,
|
||||
// same as the exposure/tonemapper controls below.
|
||||
float radius = lighting_profiles::current_rtao_radius();
|
||||
if (SliderFloat("Ambient Occlusion Radius", &radius, 0.0f, 200.0f)) {
|
||||
lighting_profiles::lab_set_rtao_radius(radius);
|
||||
}
|
||||
|
||||
float strength = lighting_profiles::current_rtao_strength();
|
||||
if (SliderFloat("Ambient Occlusion Strength", &strength, 0.0f, 2.0f)) {
|
||||
lighting_profiles::lab_set_rtao_strength(strength);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace ltp = lighting_profiles;
|
||||
using namespace ltp;
|
||||
@@ -775,16 +658,10 @@ void LabUi::show_render_options()
|
||||
|
||||
build_shadow_method_combobox();
|
||||
|
||||
build_rt_shadow_quality_combobox();
|
||||
|
||||
build_max_rt_shadow_lights_slider();
|
||||
|
||||
build_rt_shadow_bias_sliders();
|
||||
|
||||
build_shadow_penumbra_sliders();
|
||||
|
||||
build_rtao_sliders();
|
||||
|
||||
build_tone_mapper_combobox();
|
||||
|
||||
if (ltp::current_tonemapper() == TonemapperAlgorithm::PPC ||
|
||||
|
||||
@@ -44,11 +44,8 @@ class LabUi {
|
||||
void build_texture_quality_combobox();
|
||||
void build_antialiasing_combobox();
|
||||
static void build_shadow_method_combobox();
|
||||
static void build_rt_shadow_quality_combobox();
|
||||
static void build_max_rt_shadow_lights_slider();
|
||||
static void build_rt_shadow_bias_sliders();
|
||||
static void build_shadow_penumbra_sliders();
|
||||
static void build_rtao_sliders();
|
||||
void build_tone_mapper_combobox();
|
||||
void build_model_info_box(ship_info* sip, polymodel* pm) const;
|
||||
void build_subsystem_list(object* objp, ship* shipp) const;
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
#include "lab/labv2_internal.h"
|
||||
#include "lab/manager/lab_manager.h"
|
||||
#include "gamesequence/gamesequence.h"
|
||||
#include "graphics/rtao.h"
|
||||
#include "graphics/shadows.h"
|
||||
#include "starfield/starfield.h"
|
||||
|
||||
static std::unique_ptr<LabManager> LMGR;
|
||||
|
||||
@@ -16,57 +13,11 @@ const std::unique_ptr<LabManager> &getLabManager() {
|
||||
return LMGR;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// The lab changes these settings directly, so we save them when the lab opens and restore them when it closes.
|
||||
// Each LabRenderer setter that writes a game global must have a field here.
|
||||
struct LabSavedSettings {
|
||||
ShadowRenderMethod shadow_render_method;
|
||||
RTShadowQuality rt_shadow_quality;
|
||||
int max_rt_shadow_lights;
|
||||
int max_rt_shadow_local_lights;
|
||||
float rt_shadow_bias_min;
|
||||
float rt_shadow_bias_max;
|
||||
int rt_shadow_samples_override;
|
||||
float sun_angular_size_override;
|
||||
int rtao_samples;
|
||||
bool shadow_contact_hardening_enabled;
|
||||
};
|
||||
|
||||
bool Lab_settings_saved = false;
|
||||
LabSavedSettings Lab_saved_settings;
|
||||
}
|
||||
|
||||
void lab_init() {
|
||||
gr_set_clear_color(0, 0, 0);
|
||||
|
||||
Lab_saved_settings = {Shadow_render_method,
|
||||
Rt_shadow_quality,
|
||||
Max_rt_shadow_lights,
|
||||
Max_rt_shadow_local_lights,
|
||||
Rt_shadow_bias_min,
|
||||
Rt_shadow_bias_max,
|
||||
Rt_shadow_samples_override,
|
||||
Sun_angular_size_override,
|
||||
Rtao_samples,
|
||||
Shadow_contact_hardening_enabled};
|
||||
Lab_settings_saved = true;
|
||||
}
|
||||
|
||||
void lab_close(bool reset) {
|
||||
if (Lab_settings_saved) {
|
||||
Shadow_render_method = Lab_saved_settings.shadow_render_method;
|
||||
Rt_shadow_quality = Lab_saved_settings.rt_shadow_quality;
|
||||
Max_rt_shadow_lights = Lab_saved_settings.max_rt_shadow_lights;
|
||||
Max_rt_shadow_local_lights = Lab_saved_settings.max_rt_shadow_local_lights;
|
||||
Rt_shadow_bias_min = Lab_saved_settings.rt_shadow_bias_min;
|
||||
Rt_shadow_bias_max = Lab_saved_settings.rt_shadow_bias_max;
|
||||
Rt_shadow_samples_override = Lab_saved_settings.rt_shadow_samples_override;
|
||||
Sun_angular_size_override = Lab_saved_settings.sun_angular_size_override;
|
||||
Rtao_samples = Lab_saved_settings.rtao_samples;
|
||||
Shadow_contact_hardening_enabled = Lab_saved_settings.shadow_contact_hardening_enabled;
|
||||
Lab_settings_saved = false;
|
||||
}
|
||||
|
||||
if (reset) {
|
||||
LMGR.reset();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "globalincs/pstypes.h"
|
||||
#include "globalincs/flagset.h"
|
||||
#include "graphics/2d.h"
|
||||
#include "graphics/rtao.h"
|
||||
#include "graphics/shadows.h"
|
||||
#include "lighting/lighting_profiles.h"
|
||||
#include "camera/camera.h"
|
||||
@@ -131,27 +130,12 @@ public:
|
||||
Shadow_render_method = method;
|
||||
}
|
||||
|
||||
// Session-only override, same as setShadowRenderMethod -- does not touch the
|
||||
// persisted Raytraced Shadow Quality option. Safe to change at any time: the
|
||||
// only thing it gates is whether local lights are picked as shadow casters
|
||||
// while filling the light uniforms, which happens fresh every frame, so
|
||||
// nothing has to be rebuilt for the switch to take effect.
|
||||
static void setRtShadowQuality(RTShadowQuality quality) {
|
||||
Rt_shadow_quality = quality;
|
||||
}
|
||||
|
||||
// Session-only override, same as setShadowRenderMethod -- does not touch the
|
||||
// persisted Max Raytraced Shadow Lights option.
|
||||
static void setMaxRtShadowLights(int count) {
|
||||
Max_rt_shadow_lights = count;
|
||||
}
|
||||
|
||||
// Session-only override, same as setMaxRtShadowLights -- does not touch the persisted
|
||||
// Max Raytraced Local Shadow Lights option. Only consulted at RTShadowQuality::High.
|
||||
static void setMaxRtShadowLocalLights(int count) {
|
||||
Max_rt_shadow_local_lights = count;
|
||||
}
|
||||
|
||||
// Session-only overrides, same as setMaxRtShadowLights -- do not touch the
|
||||
// persisted Min/Max Raytraced Shadow Bias options. See Rt_shadow_bias_min/max
|
||||
// in shadows.h.
|
||||
@@ -163,33 +147,6 @@ public:
|
||||
Rt_shadow_bias_max = bias;
|
||||
}
|
||||
|
||||
// Ray count normally follows Shadow_quality (see shadows_rt_sample_count()), which is
|
||||
// fixed at startup because the shadow map is sized then. The ray count isn't, so the
|
||||
// lab can sweep it live through this session-only override. See
|
||||
// Rt_shadow_samples_override in shadows.h.
|
||||
static void setRtShadowSamples(int samples) {
|
||||
Rt_shadow_samples_override = samples;
|
||||
}
|
||||
|
||||
// Session-only override of every sun's $SunAngularSize (degrees of apparent
|
||||
// diameter); pass a negative value to return to the stars.tbl values. See
|
||||
// Sun_angular_size_override in starfield.h.
|
||||
static void setSunAngularSizeOverride(float degrees) {
|
||||
Sun_angular_size_override = degrees;
|
||||
}
|
||||
|
||||
// Session-only override, same as setRtShadowSamples -- does not touch the
|
||||
// persisted Raytraced Ambient Occlusion option. See Rtao_samples in rtao.h.
|
||||
static void setRtaoSamples(int samples) {
|
||||
Rtao_samples = samples;
|
||||
}
|
||||
|
||||
// Session-only override, same as setRtaoSamples -- does not touch the persisted
|
||||
// Shadow Contact Hardening option. See Shadow_contact_hardening_enabled in shadows.h.
|
||||
static void setShadowContactHardening(bool enabled) {
|
||||
Shadow_contact_hardening_enabled = enabled;
|
||||
}
|
||||
|
||||
static void setTonemapper(ltp::TonemapperAlgorithm mode) {
|
||||
ltp::lab_set_tonemapper(mode);
|
||||
}
|
||||
|
||||
@@ -97,16 +97,6 @@ float current_exposure()
|
||||
return _current.exposure;
|
||||
}
|
||||
|
||||
float current_rtao_radius()
|
||||
{
|
||||
return _current.rtao_radius;
|
||||
}
|
||||
|
||||
float current_rtao_strength()
|
||||
{
|
||||
return _current.rtao_strength;
|
||||
}
|
||||
|
||||
piecewise_power_curve_intermediates current_piecewise_intermediates()
|
||||
{
|
||||
return calc_intermediates(_current.ppc_values);
|
||||
@@ -221,16 +211,6 @@ void lab_set_exposure(float exIn)
|
||||
_current.exposure = exIn;
|
||||
}
|
||||
|
||||
void lab_set_rtao_radius(float radius)
|
||||
{
|
||||
_current.rtao_radius = radius;
|
||||
}
|
||||
|
||||
void lab_set_rtao_strength(float strength)
|
||||
{
|
||||
_current.rtao_strength = strength;
|
||||
}
|
||||
|
||||
void lab_set_tonemapper(TonemapperAlgorithm tnin)
|
||||
{
|
||||
_current.tonemapper = tnin;
|
||||
@@ -401,21 +381,6 @@ void profile::parse(const char* filename, const SCP_string& profile_name, const
|
||||
parsed |= parse_optional_float_into("$PPC Shoulder Angle:", &ppc_values.shoulder_angle);
|
||||
parsed |= parse_optional_float_into("$Exposure:", &exposure);
|
||||
|
||||
if (parse_optional_float_into("$RTAO Radius:", &rtao_radius)) {
|
||||
parsed = true;
|
||||
if (rtao_radius < 0.0f) {
|
||||
error_display(0, "$RTAO Radius: must be >= 0 (got %f); using 0.", rtao_radius);
|
||||
rtao_radius = 0.0f;
|
||||
}
|
||||
}
|
||||
if (parse_optional_float_into("$RTAO Strength:", &rtao_strength)) {
|
||||
parsed = true;
|
||||
if (rtao_strength < 0.0f) {
|
||||
error_display(0, "$RTAO Strength: must be >= 0 (got %f); using 0.", rtao_strength);
|
||||
rtao_strength = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
parsed |= adjustment::parse(filename, "$Missile light brightness:", profile_name, &missile_light_brightness);
|
||||
parsed |= adjustment::parse(filename, "$Missile light radius:", profile_name, &missile_light_radius);
|
||||
|
||||
@@ -479,9 +444,6 @@ void profile::reset()
|
||||
|
||||
exposure = 4.0f;
|
||||
|
||||
rtao_radius = 20.0f;
|
||||
rtao_strength = 1.0f;
|
||||
|
||||
missile_light_brightness.reset();
|
||||
missile_light_radius.reset();
|
||||
missile_light_radius.base = 20.0f;
|
||||
|
||||
@@ -59,13 +59,6 @@ class profile {
|
||||
TonemapperAlgorithm tonemapper;
|
||||
piecewise_power_curve_values ppc_values;
|
||||
float exposure;
|
||||
// Raytraced ambient occlusion tuning (only sampled when rtao_enabled(), see
|
||||
// graphics/rtao.h). Radius is the AO ray length in world units -- it is
|
||||
// content-scale-dependent (fighters vs. capships), which is why it lives in
|
||||
// the mod-owned lighting profile rather than a user option. Strength is an
|
||||
// exponent on the occlusion term (1 = physical, >1 darkens, <1 lightens).
|
||||
float rtao_radius;
|
||||
float rtao_strength;
|
||||
adjustment missile_light_brightness;
|
||||
adjustment missile_light_radius;
|
||||
adjustment laser_light_brightness;
|
||||
@@ -104,11 +97,7 @@ const piecewise_power_curve_values& current_piecewise_values();
|
||||
piecewise_power_curve_intermediates current_piecewise_intermediates();
|
||||
piecewise_power_curve_intermediates calc_intermediates(piecewise_power_curve_values input);
|
||||
float current_exposure();
|
||||
float current_rtao_radius();
|
||||
float current_rtao_strength();
|
||||
void lab_set_exposure(float exIn);
|
||||
void lab_set_rtao_radius(float radius);
|
||||
void lab_set_rtao_strength(float strength);
|
||||
void lab_set_tonemapper(TonemapperAlgorithm tnin);
|
||||
void lab_set_ppc(const piecewise_power_curve_values &ppcin);
|
||||
const piecewise_power_curve_values &lab_get_ppc();
|
||||
|
||||
@@ -6181,14 +6181,6 @@ void parse_one_background(background_t *background)
|
||||
sle.div_x = 1;
|
||||
sle.div_y = 1;
|
||||
|
||||
// apparent diameter in degrees, overriding both the sun's stars.tbl entry and the
|
||||
// size that would otherwise be measured from its bitmap. Optional, so missions
|
||||
// written before 26.3 simply don't have it
|
||||
if (optional_string("+AngularSize:")) {
|
||||
stuff_float(&sle.angular_size);
|
||||
sle.angular_size = stars_check_angular_size(sle.angular_size, "+AngularSize:", sle.filename);
|
||||
}
|
||||
|
||||
// add it
|
||||
background->suns.push_back(sle);
|
||||
}
|
||||
@@ -9702,19 +9694,3 @@ bool check_for_25_1_data()
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool check_for_26_3_data()
|
||||
{
|
||||
// a sun that carries its own apparent diameter (+AngularSize:) can't be represented
|
||||
// in an older mission file
|
||||
for (const auto &background : Backgrounds)
|
||||
{
|
||||
for (const auto &sun : background.suns)
|
||||
{
|
||||
if (sun.angular_size >= 0.0f)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -61,7 +61,6 @@ extern bool check_for_23_3_data();
|
||||
extern bool check_for_24_1_data();
|
||||
extern bool check_for_24_3_data();
|
||||
extern bool check_for_25_1_data();
|
||||
extern bool check_for_26_3_data();
|
||||
|
||||
#define WING_PLAYER_BASE 0x80000 // used by Fred to tell ship_index in a wing points to a player
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ int Training_message_method = 1;
|
||||
int Training_num_lines = 0;
|
||||
int Training_voice = -1;
|
||||
int Training_voice_type;
|
||||
int Training_voice_soundstream = -1;
|
||||
int Training_voice_soundstream;
|
||||
sound_handle Training_voice_snd_handle;
|
||||
int Training_flag = 0;
|
||||
int Training_failure = 0;
|
||||
@@ -699,7 +699,6 @@ void training_mission_shutdown()
|
||||
if (Training_voice >= 0) {
|
||||
if (Training_voice_type) {
|
||||
audiostream_close_file(Training_voice_soundstream, false);
|
||||
Training_voice_soundstream = -1;
|
||||
|
||||
} else {
|
||||
snd_stop(Training_voice_snd_handle);
|
||||
@@ -833,7 +832,6 @@ int message_play_training_voice(int index)
|
||||
if (Training_voice >= 0) {
|
||||
if (Training_voice_type) {
|
||||
audiostream_close_file(Training_voice_soundstream, false);
|
||||
Training_voice_soundstream = -1;
|
||||
|
||||
} else {
|
||||
snd_stop(Training_voice_snd_handle);
|
||||
@@ -855,8 +853,10 @@ int message_play_training_voice(int index)
|
||||
if ((Training_voice < 0) || !Training_voice_type || (Training_voice != index)) {
|
||||
if (Training_voice >= 0) {
|
||||
if (Training_voice_type) {
|
||||
audiostream_close_file(Training_voice_soundstream, false);
|
||||
Training_voice_soundstream = -1;
|
||||
if (Training_voice == index)
|
||||
audiostream_stop(Training_voice_soundstream, 1, 0);
|
||||
else
|
||||
audiostream_close_file(Training_voice_soundstream, false);
|
||||
|
||||
} else {
|
||||
snd_stop(Training_voice_snd_handle);
|
||||
@@ -892,7 +892,6 @@ int message_play_training_voice(int index)
|
||||
if (Training_voice >= 0) {
|
||||
if (Training_voice_type) {
|
||||
audiostream_close_file(Training_voice_soundstream, false);
|
||||
Training_voice_soundstream = -1;
|
||||
|
||||
} else {
|
||||
snd_stop(Training_voice_snd_handle);
|
||||
|
||||
@@ -1035,17 +1035,6 @@ int Fred_mission_save::save_bitmaps()
|
||||
required_string_fred("+Scale:");
|
||||
parse_comments();
|
||||
fout(" %f", sle->scale_x);
|
||||
|
||||
// apparent diameter, only written when this mission actually sets one; see
|
||||
// check_for_26_3_data()
|
||||
fso_comment_push(";;FSO 26.3.0;;");
|
||||
FRED_ENSURE_PROPERTY_VERSION_WITH_DEFAULT_F("+AngularSize:",
|
||||
1,
|
||||
";;FSO 26.3.0;;",
|
||||
SUN_ANGULAR_SIZE_UNSPECIFIED,
|
||||
" %f",
|
||||
sle->angular_size);
|
||||
fso_comment_pop();
|
||||
}
|
||||
|
||||
// save background bitmaps by filename
|
||||
@@ -3182,15 +3171,7 @@ void Fred_mission_save::save_mission_internal(const char* pathname)
|
||||
auto version_24_1 = gameversion::version(24, 1);
|
||||
auto version_24_3 = gameversion::version(24, 3);
|
||||
auto version_25_1 = gameversion::version(25, 1);
|
||||
auto version_26_3 = gameversion::version(26, 3);
|
||||
if (MISSION_VERSION >= version_26_3) {
|
||||
Warning(LOCATION,
|
||||
"Notify an SCP coder: now that the required mission version is at least 26.3, the check_for_26_3_data(), "
|
||||
"check_for_25_1_data(), check_for_24_3_data(), check_for_24_1_data(), and check_for_23_3_data() code can "
|
||||
"be removed");
|
||||
} else if (check_for_26_3_data()) {
|
||||
The_mission.required_fso_version = version_26_3;
|
||||
} else if (MISSION_VERSION >= version_25_1) {
|
||||
if (MISSION_VERSION >= version_25_1) {
|
||||
Warning(LOCATION,
|
||||
"Notify an SCP coder: now that the required mission version is at least 25.1, the check_for_25_1_data(), "
|
||||
"check_for_24_3_data(), check_for_24_1_data(), and check_for_23_3_data() code can be removed");
|
||||
|
||||
@@ -651,7 +651,7 @@ void cmd_brief_close()
|
||||
for (i=0; i<Cur_cmd_brief->num_stages; i++) {
|
||||
if (Cur_cmd_brief->stage[i].wave >= 0)
|
||||
audiostream_close_file(Cur_cmd_brief->stage[i].wave, 0);
|
||||
Cur_cmd_brief->stage[i].wave = -1;
|
||||
|
||||
}
|
||||
|
||||
// so that the same ani will reload properly upon return
|
||||
|
||||
@@ -33,8 +33,6 @@
|
||||
#include "io/timer.h"
|
||||
#include "lighting/lighting.h"
|
||||
#include "lighting/lighting_profiles.h"
|
||||
#include "starfield/starfield.h"
|
||||
#include "starfield/sun_disc.h"
|
||||
#include "missionui/chatbox.h"
|
||||
#include "missionui/missionbrief.h"
|
||||
#include "missionui/missionscreencommon.h"
|
||||
@@ -1991,23 +1989,16 @@ void draw_model_rotating(model_render_params *render_info, int ship_class, int m
|
||||
*/
|
||||
void common_setup_room_lights()
|
||||
{
|
||||
// These stand in for a sun rather than for a point source, so they get a sun's apparent
|
||||
// size: for a directional light source_radius is the tangent of the angular radius, and
|
||||
// it is what sizes both the raytraced penumbra cone and the shadow map's filter width
|
||||
// (see shadow_smoothness_scale() in shadows.cpp). Leaving it at 0 would read as a light
|
||||
// with no extent at all and give these rooms hard, aliased shadow edges.
|
||||
const float room_light_source_radius = sun_disc_tangent_from_diameter(SUN_ANGULAR_SIZE_SOL);
|
||||
|
||||
light_reset();
|
||||
auto tempv = vm_vec_new(-1.0f,0.3f,-1.0f);
|
||||
auto tempc = hdr_color(1.0f,0.95f,0.9f, 0.0f, 1.5f);
|
||||
light_add_directional(&tempv,-1,false,&tempc,room_light_source_radius);
|
||||
light_add_directional(&tempv,-1,false,&tempc);
|
||||
tempv.xyz={-0.4f,0.4f,1.1f};
|
||||
tempc = hdr_color(0.788f,0.886f,1.0f,0.0f,1.5f);
|
||||
light_add_directional(&tempv,-1,false,&tempc,room_light_source_radius);
|
||||
light_add_directional(&tempv,-1,false,&tempc);
|
||||
tempv.xyz={0.4f,0.1f,0.4f};
|
||||
tempc = hdr_color(1.0f,1.0f,1.0f,0.0f,0.4f);
|
||||
light_add_directional(&tempv,-1,false,&tempc,room_light_source_radius);
|
||||
light_add_directional(&tempv,-1,false,&tempc);
|
||||
gr_set_ambient_light(53, 53, 53);
|
||||
}
|
||||
|
||||
|
||||
@@ -1248,22 +1248,6 @@ int eval_weapon_flag_for_game_type(int weapon_flags)
|
||||
return rval;
|
||||
}
|
||||
|
||||
static bool wl_bank_allows_weapon(const ship_info* sip, int bank, int weapon_class)
|
||||
{
|
||||
// an empty bank has nothing to place; this check runs on the weapon coming back from a swap
|
||||
if (weapon_class < 0)
|
||||
return true;
|
||||
|
||||
if (!eval_weapon_flag_for_game_type(sip->allowed_weapons[weapon_class]))
|
||||
return false;
|
||||
|
||||
// allowed_weapons is built from the bank lists, so a class without them never gets here
|
||||
if (!eval_weapon_flag_for_game_type(sip->restricted_loadout_flag[bank]))
|
||||
return true;
|
||||
|
||||
return eval_weapon_flag_for_game_type(sip->allowed_bank_restricted_weapons[bank][weapon_class]) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the weapon class shown at the given on-screen row of a scrolling icon list,
|
||||
* or -1 if that row is past the end of the list.
|
||||
@@ -3700,30 +3684,34 @@ int wl_swap_slot_slot(int from_bank, int to_bank, int ship_slot, interface_snd_i
|
||||
ship_info *sip = &Ship_info[slot->ship_class];
|
||||
|
||||
// check the to-bank first
|
||||
if (!wl_bank_allows_weapon(sip, to_bank, slot->wep[from_bank])) {
|
||||
SCP_string txt;
|
||||
sprintf(txt, XSTR("This bank is unable to carry %s weaponry", 1628), Weapon_info[slot->wep[from_bank]].get_display_name());
|
||||
if (eval_weapon_flag_for_game_type(sip->restricted_loadout_flag[to_bank])) {
|
||||
if (!eval_weapon_flag_for_game_type(sip->allowed_bank_restricted_weapons[to_bank][slot->wep[from_bank]])) {
|
||||
SCP_string txt;
|
||||
sprintf(txt, XSTR("This bank is unable to carry %s weaponry", 1628), Weapon_info[slot->wep[from_bank]].get_display_name());
|
||||
|
||||
if ( !(Game_mode & GM_MULTIPLAYER) || (Netgame.host == pl) ) {
|
||||
popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, txt.c_str());
|
||||
} else if (pl != nullptr) {
|
||||
send_game_chat_packet(Netgame.host, txt.c_str(), MULTI_MSG_TARGET, pl, nullptr, 1);
|
||||
if ( !(Game_mode & GM_MULTIPLAYER) || (Netgame.host == pl) ) {
|
||||
popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, txt.c_str());
|
||||
} else if (pl != NULL) {
|
||||
send_game_chat_packet(Netgame.host, txt.c_str(), MULTI_MSG_TARGET, pl, nullptr, 1);
|
||||
}
|
||||
|
||||
return forced_update;
|
||||
}
|
||||
|
||||
return forced_update;
|
||||
}
|
||||
|
||||
// check the from-bank
|
||||
if (!wl_bank_allows_weapon(sip, from_bank, slot->wep[to_bank])) {
|
||||
// going from "from" to "to" is valid (from previous if), but going the other way isn't...
|
||||
// so return the "to" to the list and just move the "from"
|
||||
if (eval_weapon_flag_for_game_type(sip->restricted_loadout_flag[from_bank])) {
|
||||
if (!eval_weapon_flag_for_game_type(sip->allowed_bank_restricted_weapons[from_bank][slot->wep[to_bank]])) {
|
||||
// going from "from" to "to" is valid (from previous if), but going the other way isn't...
|
||||
// so return the "to" to the list and just move the "from"
|
||||
|
||||
// put to_bank back into list
|
||||
(*Wl_pool)[slot->wep[to_bank]] += slot->wep_count[to_bank]; // return to list
|
||||
slot->wep[to_bank] = -1; // remove from slot
|
||||
slot->wep_count[to_bank] = 0;
|
||||
*sound=InterfaceSounds::ICON_DROP; // unless it changes later
|
||||
forced_update = 1; // because we can't return right away
|
||||
// put to_bank back into list
|
||||
(*Wl_pool)[slot->wep[to_bank]] += slot->wep_count[to_bank]; // return to list
|
||||
slot->wep[to_bank] = -1; // remove from slot
|
||||
slot->wep_count[to_bank] = 0;
|
||||
*sound=InterfaceSounds::ICON_DROP; // unless it changes later
|
||||
forced_update = 1; // because we can't return right away
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3851,17 +3839,19 @@ int wl_grab_from_list(int from_list, int to_bank, int ship_slot, interface_snd_i
|
||||
ship_info *sip = &Ship_info[slot->ship_class];
|
||||
|
||||
// ensure that this bank will accept the weapon...
|
||||
if (!wl_bank_allows_weapon(sip, to_bank, from_list)) {
|
||||
SCP_string txt;
|
||||
sprintf(txt, XSTR("This bank is unable to carry %s weaponry", 1628), Weapon_info[from_list].get_display_name());
|
||||
if (eval_weapon_flag_for_game_type(sip->restricted_loadout_flag[to_bank])) {
|
||||
if (!eval_weapon_flag_for_game_type(sip->allowed_bank_restricted_weapons[to_bank][from_list])) {
|
||||
SCP_string txt;
|
||||
sprintf(txt, XSTR("This bank is unable to carry %s weaponry", 1628), Weapon_info[from_list].get_display_name());
|
||||
|
||||
if ( !(Game_mode & GM_MULTIPLAYER) || (Netgame.host == pl) ) {
|
||||
popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, txt.c_str());
|
||||
} else if (pl != nullptr) {
|
||||
send_game_chat_packet(Netgame.host, txt.c_str(), MULTI_MSG_TARGET, pl, nullptr, 1);
|
||||
if ( !(Game_mode & GM_MULTIPLAYER) || (Netgame.host == pl) ) {
|
||||
popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, txt.c_str());
|
||||
} else if (pl != NULL) {
|
||||
send_game_chat_packet(Netgame.host, txt.c_str(), MULTI_MSG_TARGET, pl, nullptr, 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// we will have some sort of success from this point
|
||||
@@ -3927,17 +3917,19 @@ int wl_swap_list_slot(int from_list, int to_bank, int ship_slot, interface_snd_i
|
||||
ship_info *sip = &Ship_info[slot->ship_class];
|
||||
|
||||
// ensure that this bank will accept the weapon
|
||||
if (!wl_bank_allows_weapon(sip, to_bank, from_list)) {
|
||||
SCP_string txt;
|
||||
sprintf(txt, XSTR("This bank is unable to carry %s weaponry", 1628), Weapon_info[from_list].get_display_name());
|
||||
if (eval_weapon_flag_for_game_type(sip->restricted_loadout_flag[to_bank])) {
|
||||
if (!eval_weapon_flag_for_game_type(sip->allowed_bank_restricted_weapons[to_bank][from_list])) {
|
||||
SCP_string txt;
|
||||
sprintf(txt, XSTR("This bank is unable to carry %s weaponry", 1628), Weapon_info[from_list].get_display_name());
|
||||
|
||||
if ( !(Game_mode & GM_MULTIPLAYER) || (Netgame.host == pl) ) {
|
||||
popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, txt.c_str());
|
||||
} else if (pl != nullptr) {
|
||||
send_game_chat_packet(Netgame.host, txt.c_str(), MULTI_MSG_TARGET, pl, nullptr, 1);
|
||||
if ( !(Game_mode & GM_MULTIPLAYER) || (Netgame.host == pl) ) {
|
||||
popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, txt.c_str());
|
||||
} else if (pl != NULL) {
|
||||
send_game_chat_packet(Netgame.host, txt.c_str(), MULTI_MSG_TARGET, pl, nullptr, 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// dump slot weapon back into list
|
||||
|
||||
@@ -853,12 +853,6 @@ void parse_mod_table(const char *filename)
|
||||
}
|
||||
}
|
||||
|
||||
// Per-cascade shadow map filter radius, in shadow map UV units, for a
|
||||
// Sol-sized sun. It is no longer the final width: shadow_cascade_params_bind()
|
||||
// scales these by the sun's apparent size relative to Sol, so that the same
|
||||
// $SunAngularSize: drives both shadow-mapped and raytraced softness. A mod that
|
||||
// tuned these against retail/MediaVPs sun art keeps the look it tuned for --
|
||||
// see shadow_smoothness_scale() in shadows.cpp for why Sol is the reference.
|
||||
if (optional_string("$Shadow Smoothness Factor:")) {
|
||||
SCP_vector<float> smoothness;
|
||||
stuff_float_list(smoothness);
|
||||
|
||||
@@ -440,6 +440,7 @@ namespace animation {
|
||||
|
||||
// rotation -----
|
||||
|
||||
submodel->canonical_prev_orient = submodel->canonical_orient;
|
||||
submodel->canonical_orient = data.orientation;
|
||||
|
||||
matrix delta;
|
||||
@@ -452,6 +453,7 @@ namespace animation {
|
||||
|
||||
// translation -----
|
||||
|
||||
submodel->canonical_prev_offset = submodel->canonical_offset;
|
||||
submodel->canonical_offset = data.position;
|
||||
|
||||
vec3d delta_vec;
|
||||
@@ -469,6 +471,7 @@ namespace animation {
|
||||
if (!submodel)
|
||||
return;
|
||||
|
||||
submodel->canonical_prev_orient = submodel->canonical_orient;
|
||||
submodel->current_turn_rate = 0.0f;
|
||||
}
|
||||
|
||||
@@ -482,8 +485,7 @@ namespace animation {
|
||||
|
||||
if(!submodel.second->flags[Model::Submodel_flags::Can_move]){
|
||||
mprintf(("Submodel %s of model %s is animated and has movement enabled.\n", submodel.second->name, model_get(pmi->model_num)->filename));
|
||||
auto pm = model_get(pmi->model_num);
|
||||
model_set_submodel_can_move(pm, static_cast<int>(submodel.second - pm->submodel.get()));
|
||||
submodel.second->flags.set(Model::Submodel_flags::Can_move);
|
||||
|
||||
if (submodel.second->rotation_type == MOVEMENT_TYPE_NONE) {
|
||||
submodel.second->rotation_type = MOVEMENT_TYPE_TRIGGERED;
|
||||
@@ -616,6 +618,7 @@ namespace animation {
|
||||
|
||||
// rotation -----
|
||||
|
||||
submodel->canonical_prev_orient = submodel->canonical_orient;
|
||||
submodel->canonical_orient = data.orientation;
|
||||
|
||||
submodel->rotation_axis = sm->rotation_axis;
|
||||
@@ -628,6 +631,7 @@ namespace animation {
|
||||
|
||||
// translation -----
|
||||
|
||||
submodel->canonical_prev_offset = submodel->canonical_offset;
|
||||
submodel->canonical_offset = data.position;
|
||||
|
||||
submodel->translation_axis = sm->translation_axis;
|
||||
|
||||
+19
-11
@@ -121,6 +121,7 @@ struct model_electrical_arc : electrical_arc
|
||||
struct submodel_instance
|
||||
{
|
||||
float cur_angle = 0.0f; // The current angle this thing is turned to.
|
||||
float prev_angle = 0.0f;
|
||||
float turret_idle_angle = 0.0f; // If this is a turret, this is the expected idling angle of the submodel
|
||||
|
||||
float current_turn_rate = 0.0f;
|
||||
@@ -129,6 +130,7 @@ struct submodel_instance
|
||||
TIMESTAMP stepped_rotation_started;
|
||||
|
||||
float cur_offset = 0.0f;
|
||||
float prev_offset = 0.0f;
|
||||
|
||||
float current_shift_rate = 0.0f;
|
||||
float desired_shift_rate = 0.0f;
|
||||
@@ -138,10 +140,8 @@ struct submodel_instance
|
||||
bool blown_off = false; // If set, this subobject is not rendered or used for collision
|
||||
|
||||
// These fields are the true standard reference for submodel rotation. They should seldom be read directly
|
||||
// and should almost never be written directly. In most cases, coders should prefer cur_angle.
|
||||
// and should almost never be written directly. In most cases, coders should prefer cur_angle and prev_angle.
|
||||
matrix canonical_orient = vmd_identity_matrix;
|
||||
// The pose at the end of last frame; written only by model_instance_snapshot_prev_all (for Can_move submodels),
|
||||
// by direct pose setters (for others, which the snapshot skips) and copies/resets of a whole pose, never by code that moves the submodel
|
||||
matrix canonical_prev_orient = vmd_identity_matrix;
|
||||
// similarly for translation
|
||||
vec3d canonical_offset = vmd_zero_vector;
|
||||
@@ -1072,23 +1072,25 @@ model_memory_stats model_get_memory_stats();
|
||||
int num_model_instances();
|
||||
polymodel_instance* model_get_instance(int model_instance_num);
|
||||
|
||||
// Records every instance's movable submodel poses as the previous frame's. This is the only per-frame writer of the
|
||||
// canonical_prev_* members; anything that moves a submodel only writes the current pose.
|
||||
void model_instance_snapshot_prev_all();
|
||||
// Flags a submodel, and with it all its children, as Can_move
|
||||
void model_set_submodel_can_move(polymodel* pm, int submodel_num);
|
||||
|
||||
// routine to copy subsystems. Must be called when subsystems sets are the same -- see ship.cpp
|
||||
void model_copy_subsystems(int n_subsystems, model_subsystem *d_sp, model_subsystem *s_sp);
|
||||
|
||||
// If MR_FLAG_OUTLINE bit set this color will be used for outlines.
|
||||
// This defaults to black.
|
||||
void model_set_outline_color(int r, int g, int b);
|
||||
|
||||
// IF MR_LOCK_DETAIL is set, then it will always draw detail level 'n'
|
||||
// This defaults to 0. (0=highest, larger=lower)
|
||||
void model_set_detail_level(int n);
|
||||
|
||||
// Flags you can pass to model_render
|
||||
#define MR_NORMAL (0) // Draw a normal object
|
||||
#define MR_SHOW_OUTLINE (1<<0) // Draw the object in outline mode. Color specified by model_render_params::set_color
|
||||
#define MR_SHOW_OUTLINE (1<<0) // Draw the object in outline mode. Color specified by model_set_outline_color
|
||||
#define MR_SKYBOX (1<<1) // Draw as a skybox
|
||||
#define MR_DESATURATED (1<<2) // Draw model in monochrome using outline color
|
||||
#define MR_STENCIL_WRITE (1<<3) // Write stencil buffere where the model was rendered
|
||||
#define MR_STENCIL_READ (1<<4) // Only draw pixels of the model where the stencil buffer has the right value (see MR_STENCIL_WRITE)
|
||||
#define MR_SHOW_THRUSTERS (1<<5) // Show the engine thrusters. See model_render_params::set_thruster_info for how long it draws.
|
||||
#define MR_SHOW_THRUSTERS (1<<5) // Show the engine thrusters. See model_set_thrust for how long it draws.
|
||||
#define MR_NO_COLOR_WRITES (1<<6) // Don't write anything to the color buffers (used when setting the stencil buffer)
|
||||
#define MR_NO_POLYS (1<<7) // Don't draw the polygons.
|
||||
#define MR_NO_LIGHTING (1<<8) // Don't perform any lighting on the model.
|
||||
@@ -1179,6 +1181,7 @@ bool submodel_is_destroyed_form(const char *name);
|
||||
extern int modelstats_num_polys;
|
||||
extern int modelstats_num_polys_drawn;
|
||||
extern int modelstats_num_verts;
|
||||
extern int modelstats_num_sortnorms;
|
||||
#endif
|
||||
|
||||
// Tries to move joints so that the turret points to the point dst.
|
||||
@@ -1468,6 +1471,10 @@ typedef struct mst_info {
|
||||
{}
|
||||
} mst_info;
|
||||
|
||||
// scale the engines thrusters by this much
|
||||
// Only enabled if MR_SHOW_THRUSTERS is on
|
||||
void model_set_thrust(int model_num = -1, mst_info *mst = NULL);
|
||||
|
||||
|
||||
//=======================================================================================
|
||||
// Finds the closest point on a model to a point in space. Actually only finds a point
|
||||
@@ -1528,6 +1535,7 @@ void model_draw_dock_points_htl(int model_num);
|
||||
bool model_interp_config_buffer(indexed_vertex_source *vert_src, vertex_buffer *vb, bool update_ibuffer_only);
|
||||
bool model_interp_pack_buffer(indexed_vertex_source *vert_src, vertex_buffer *vb);
|
||||
void model_interp_submit_buffers(indexed_vertex_source *vert_src, size_t vertex_stride);
|
||||
void model_allocate_interp_data(uint n_verts = 0, uint n_norms = 0);
|
||||
|
||||
void glowpoint_init();
|
||||
SCP_vector<glow_point_bank_override>::iterator get_glowpoint_bank_override_by_name(const char* name);
|
||||
|
||||
Loaded 100 of 171 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user