Render temporal scene with motion, resolve and sharp UI
Native and manual checks / native (ubuntu-24.04) (push) Failing after 37s
Native and manual checks / manual (push) Successful in 28s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
Native and manual checks / native (ubuntu-24.04) (push) Failing after 37s
Native and manual checks / manual (push) Successful in 28s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
This commit is contained in:
@@ -17,6 +17,24 @@ foreach(FASET_ENTRY vertexMain fragmentMain shadowMain)
|
|||||||
DEPENDS "${PROJECT_SOURCE_DIR}/shaders/baseline.slang" "${PROJECT_SOURCE_DIR}/tools/compile_shader.py" VERBATIM)
|
DEPENDS "${PROJECT_SOURCE_DIR}/shaders/baseline.slang" "${PROJECT_SOURCE_DIR}/tools/compile_shader.py" VERBATIM)
|
||||||
list(APPEND FASET_SHADER_OUTPUTS "${FASET_SHADER_OUTPUT}" "${FASET_SHADER_DIRECTORY}/${FASET_ENTRY}.reflection.json")
|
list(APPEND FASET_SHADER_OUTPUTS "${FASET_SHADER_OUTPUT}" "${FASET_SHADER_DIRECTORY}/${FASET_ENTRY}.reflection.json")
|
||||||
endforeach()
|
endforeach()
|
||||||
|
foreach(FASET_ENTRY temporalVertexMain temporalFragmentMain)
|
||||||
|
set(FASET_SHADER_OUTPUT "${FASET_SHADER_DIRECTORY}/${FASET_ENTRY}.spv")
|
||||||
|
add_custom_command(OUTPUT "${FASET_SHADER_OUTPUT}" "${FASET_SHADER_DIRECTORY}/${FASET_ENTRY}.reflection.json"
|
||||||
|
COMMAND "${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/tools/compile_shader.py"
|
||||||
|
--compiler "${SLANGC_EXECUTABLE}" --source "${PROJECT_SOURCE_DIR}/shaders/baseline.slang"
|
||||||
|
--entry "${FASET_ENTRY}" --output "${FASET_SHADER_DIRECTORY}"
|
||||||
|
BYPRODUCTS "${FASET_SHADER_DIRECTORY}/${FASET_ENTRY}.slang-reflection.json"
|
||||||
|
DEPENDS "${PROJECT_SOURCE_DIR}/shaders/baseline.slang" "${PROJECT_SOURCE_DIR}/tools/compile_shader.py" VERBATIM)
|
||||||
|
list(APPEND FASET_SHADER_OUTPUTS "${FASET_SHADER_OUTPUT}" "${FASET_SHADER_DIRECTORY}/${FASET_ENTRY}.reflection.json")
|
||||||
|
endforeach()
|
||||||
|
set(FASET_SHADER_OUTPUT "${FASET_SHADER_DIRECTORY}/gpuTemporalVertexMain.spv")
|
||||||
|
add_custom_command(OUTPUT "${FASET_SHADER_OUTPUT}" "${FASET_SHADER_DIRECTORY}/gpuTemporalVertexMain.reflection.json"
|
||||||
|
COMMAND "${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/tools/compile_shader.py"
|
||||||
|
--compiler "${SLANGC_EXECUTABLE}" --source "${PROJECT_SOURCE_DIR}/shaders/gpu_scene.slang"
|
||||||
|
--entry gpuTemporalVertexMain --define FASET_GPU_GRAPHICS=1 --output "${FASET_SHADER_DIRECTORY}"
|
||||||
|
BYPRODUCTS "${FASET_SHADER_DIRECTORY}/gpuTemporalVertexMain.slang-reflection.json"
|
||||||
|
DEPENDS "${PROJECT_SOURCE_DIR}/shaders/gpu_scene.slang" "${PROJECT_SOURCE_DIR}/tools/compile_shader.py" VERBATIM)
|
||||||
|
list(APPEND FASET_SHADER_OUTPUTS "${FASET_SHADER_OUTPUT}" "${FASET_SHADER_DIRECTORY}/gpuTemporalVertexMain.reflection.json")
|
||||||
foreach(FASET_ENTRY gpuVertexMain gpuShadowMain gpuCullMain gpuHzbMain gpuPostCullMain)
|
foreach(FASET_ENTRY gpuVertexMain gpuShadowMain gpuCullMain gpuHzbMain gpuPostCullMain)
|
||||||
if(FASET_ENTRY STREQUAL "gpuVertexMain" OR FASET_ENTRY STREQUAL "gpuShadowMain")
|
if(FASET_ENTRY STREQUAL "gpuVertexMain" OR FASET_ENTRY STREQUAL "gpuShadowMain")
|
||||||
set(FASET_GPU_DEFINE FASET_GPU_GRAPHICS=1)
|
set(FASET_GPU_DEFINE FASET_GPU_GRAPHICS=1)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <faset/render/temporal.hpp>
|
||||||
|
|
||||||
namespace faset::render {
|
namespace faset::render {
|
||||||
using Vec2 = std::array<float, 2>;
|
using Vec2 = std::array<float, 2>;
|
||||||
@@ -146,6 +147,8 @@ struct RendererConfig {
|
|||||||
bool headless{false};
|
bool headless{false};
|
||||||
bool validation{true};
|
bool validation{true};
|
||||||
VisibilityMode visibility_mode{VisibilityMode::Direct};
|
VisibilityMode visibility_mode{VisibilityMode::Direct};
|
||||||
|
TemporalMode temporal_mode{TemporalMode::Off};
|
||||||
|
float render_scale{1.f};
|
||||||
// GPU counter readback is diagnostic-only; normal visibility uses no CPU feedback.
|
// GPU counter readback is diagnostic-only; normal visibility uses no CPU feedback.
|
||||||
bool visibility_diagnostics{false};
|
bool visibility_diagnostics{false};
|
||||||
// Optional isolated shader bundle, useful for editor preview and shader reload tests.
|
// Optional isolated shader bundle, useful for editor preview and shader reload tests.
|
||||||
@@ -204,6 +207,16 @@ struct FrameStats {
|
|||||||
double gpu_main_cull_ms{}, gpu_main_raster_ms{}, gpu_hzb_ms{};
|
double gpu_main_cull_ms{}, gpu_main_raster_ms{}, gpu_hzb_ms{};
|
||||||
double gpu_post_cull_ms{}, gpu_post_raster_ms{};
|
double gpu_post_cull_ms{}, gpu_post_raster_ms{};
|
||||||
double gpu_sun_shadow_ms{}, gpu_local_shadow_ms{};
|
double gpu_sun_shadow_ms{}, gpu_local_shadow_ms{};
|
||||||
|
TemporalMode requested_temporal_mode{TemporalMode::Off};
|
||||||
|
TemporalMode effective_temporal_mode{TemporalMode::Off};
|
||||||
|
TemporalFallbackReason temporal_fallback_reason{TemporalFallbackReason::None};
|
||||||
|
TemporalResetReason temporal_reset_reason{TemporalResetReason::FirstFrame};
|
||||||
|
bool temporal_history_valid{};
|
||||||
|
std::uint32_t temporal_valid_motion_instances{};
|
||||||
|
std::uint32_t temporal_internal_width{}, temporal_internal_height{};
|
||||||
|
std::array<float, 2> temporal_jitter{};
|
||||||
|
double gpu_temporal_resolve_ms{}, gpu_temporal_composite_ms{}, gpu_ui_ms{};
|
||||||
|
std::vector<std::string> graph_passes;
|
||||||
std::string effective_lighting_path{"forward"};
|
std::string effective_lighting_path{"forward"};
|
||||||
std::string device;
|
std::string device;
|
||||||
};
|
};
|
||||||
@@ -224,6 +237,8 @@ class Renderer {
|
|||||||
void resize(std::uint32_t width, std::uint32_t height);
|
void resize(std::uint32_t width, std::uint32_t height);
|
||||||
void set_visibility_mode(VisibilityMode);
|
void set_visibility_mode(VisibilityMode);
|
||||||
VisibilityMode visibility_mode() const;
|
VisibilityMode visibility_mode() const;
|
||||||
|
void set_temporal_mode(TemporalMode mode, float render_scale = 1.f);
|
||||||
|
TemporalMode temporal_mode() const;
|
||||||
void set_visibility_diagnostics(bool enabled);
|
void set_visibility_diagnostics(bool enabled);
|
||||||
// Reads the most recently completed HZB mip for editor diagnostics only.
|
// Reads the most recently completed HZB mip for editor diagnostics only.
|
||||||
// Normal visibility decisions remain entirely on the GPU.
|
// Normal visibility decisions remain entirely on the GPU.
|
||||||
|
|||||||
+55
-2
@@ -122,8 +122,7 @@ float3 directBRDF(float3 base, float rough, float metal, float3 n, float3 view,
|
|||||||
float3 spec=d*g*fresnel/max(4.0*nv*nl,0.001);
|
float3 spec=d*g*fresnel/max(4.0*nv*nl,0.001);
|
||||||
return ((1.0-fresnel)*(1.0-metal)*base/pi+spec)*nl;
|
return ((1.0-fresnel)*(1.0-metal)*base/pi+spec)*nl;
|
||||||
}
|
}
|
||||||
[shader("fragment")]
|
float4 shadeScene(VertexOutput v) {
|
||||||
float4 fragmentMain(VertexOutput v) : SV_Target {
|
|
||||||
float4 sampled = colorMap.Sample(colorSampler, v.uv);
|
float4 sampled = colorMap.Sample(colorSampler, v.uv);
|
||||||
if (dot(v.normal,v.normal) < 1e-12) {
|
if (dot(v.normal,v.normal) < 1e-12) {
|
||||||
// UI/sprite tint is in display space; sRGB textures were decoded by Vulkan.
|
// UI/sprite tint is in display space; sRGB textures were decoded by Vulkan.
|
||||||
@@ -202,3 +201,57 @@ float4 fragmentMain(VertexOutput v) : SV_Target {
|
|||||||
linear=linear/(1.0+linear);
|
linear=linear/(1.0+linear);
|
||||||
return float4(pow(max(linear,0),float3(1.0/2.2)),base.a);
|
return float4(pow(max(linear,0),float3(1.0/2.2)),base.a);
|
||||||
}
|
}
|
||||||
|
[shader("fragment")]
|
||||||
|
float4 fragmentMain(VertexOutput v) : SV_Target { return shadeScene(v); }
|
||||||
|
|
||||||
|
struct TemporalVertexInput {
|
||||||
|
float4 clip : POSITION;
|
||||||
|
float3 world : TEXCOORD0;
|
||||||
|
float3 normal : NORMAL;
|
||||||
|
float4 color : COLOR0;
|
||||||
|
float2 material : TEXCOORD1;
|
||||||
|
float2 uv : TEXCOORD2;
|
||||||
|
float4 previousClip : TEXCOORD3;
|
||||||
|
float motionValid : TEXCOORD4;
|
||||||
|
};
|
||||||
|
struct TemporalVertexOutput {
|
||||||
|
float4 position : SV_Position;
|
||||||
|
float3 world : TEXCOORD0;
|
||||||
|
float3 normal : NORMAL;
|
||||||
|
float4 color : COLOR0;
|
||||||
|
float2 material : TEXCOORD1;
|
||||||
|
float2 uv : TEXCOORD2;
|
||||||
|
float4 previousClip : TEXCOORD3;
|
||||||
|
float4 currentClip : TEXCOORD4;
|
||||||
|
float motionValid : TEXCOORD5;
|
||||||
|
};
|
||||||
|
[shader("vertex")]
|
||||||
|
TemporalVertexOutput temporalVertexMain(TemporalVertexInput v) {
|
||||||
|
TemporalVertexOutput o;
|
||||||
|
o.position=v.clip; o.world=v.world; o.normal=v.normal; o.color=v.color;
|
||||||
|
o.material=v.material; o.uv=v.uv; o.previousClip=v.previousClip;
|
||||||
|
o.currentClip=v.clip; o.motionValid=v.motionValid;
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
struct TemporalFragmentOutput {
|
||||||
|
float4 color : SV_Target0;
|
||||||
|
float4 velocity : SV_Target1;
|
||||||
|
};
|
||||||
|
[shader("fragment")]
|
||||||
|
TemporalFragmentOutput temporalFragmentMain(TemporalVertexOutput v) {
|
||||||
|
VertexOutput shading;
|
||||||
|
shading.position=v.position; shading.world=v.world; shading.normal=v.normal;
|
||||||
|
shading.color=v.color; shading.material=v.material; shading.uv=v.uv;
|
||||||
|
TemporalFragmentOutput result;
|
||||||
|
result.color=shadeScene(shading);
|
||||||
|
result.velocity=float4(0);
|
||||||
|
if (v.motionValid > .5 && result.color.a >= .999 &&
|
||||||
|
all(isfinite(v.currentClip)) && all(isfinite(v.previousClip)) &&
|
||||||
|
v.currentClip.w > 0 && v.previousClip.w > 0) {
|
||||||
|
result.velocity.xy=(v.currentClip.xy / v.currentClip.w -
|
||||||
|
v.previousClip.xy / v.previousClip.w) * .5;
|
||||||
|
result.velocity.z=v.previousClip.z / v.previousClip.w;
|
||||||
|
result.velocity.w=1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ struct InstanceRecord {
|
|||||||
float4 previousExtent; // 192..207
|
float4 previousExtent; // 192..207
|
||||||
uint4 metadata; // 208..223: x bits 0=HZB, 1=temporal prior valid; y=stableSlot,
|
uint4 metadata; // 208..223: x bits 0=HZB, 1=temporal prior valid; y=stableSlot,
|
||||||
// z=generation low 32, w=generation high 32 (zero = untracked)
|
// z=generation low 32, w=generation high 32 (zero = untracked)
|
||||||
|
column_major float4x4 previousModel; // 224..287
|
||||||
};
|
};
|
||||||
struct ViewRecord {
|
struct ViewRecord {
|
||||||
column_major float4x4 currentViewProjection; // 0..63
|
column_major float4x4 currentViewProjection; // 0..63
|
||||||
@@ -81,6 +82,42 @@ GpuSceneOutput gpuVertexMain(GpuSceneVertex vertex, uint drawInstance : SV_Vulka
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct GpuSceneTemporalOutput {
|
||||||
|
float4 position : SV_Position;
|
||||||
|
float3 world : TEXCOORD0;
|
||||||
|
float3 normal : NORMAL;
|
||||||
|
float4 color : COLOR0;
|
||||||
|
float2 material : TEXCOORD1;
|
||||||
|
float2 uv : TEXCOORD2;
|
||||||
|
float4 previousClip : TEXCOORD3;
|
||||||
|
float4 currentClip : TEXCOORD4;
|
||||||
|
float motionValid : TEXCOORD5;
|
||||||
|
};
|
||||||
|
[shader("vertex")]
|
||||||
|
GpuSceneTemporalOutput gpuTemporalVertexMain(GpuSceneVertex vertex,
|
||||||
|
uint drawInstance : SV_VulkanInstanceID) {
|
||||||
|
InstanceRecord instance = gfxInstances[gfxVisibleIds[gpuFrame.drawInfo.x + drawInstance]];
|
||||||
|
ViewRecord view = gfxViews[0];
|
||||||
|
float4 local = float4(vertex.position, 1);
|
||||||
|
float4 world = mul(instance.model, local);
|
||||||
|
GpuSceneTemporalOutput output;
|
||||||
|
output.position = mul(view.currentViewProjection, world);
|
||||||
|
output.currentClip = output.position;
|
||||||
|
output.previousClip = mul(view.previousViewProjection,
|
||||||
|
mul(instance.previousModel, local));
|
||||||
|
output.motionValid = (instance.metadata.x & 2u) != 0u ? 1.0 : 0.0;
|
||||||
|
output.world = world.xyz;
|
||||||
|
float3 normal = float3(dot(instance.normalRow0.xyz, vertex.normal),
|
||||||
|
dot(instance.normalRow1.xyz, vertex.normal),
|
||||||
|
dot(instance.normalRow2.xyz, vertex.normal));
|
||||||
|
float normalLength = length(normal);
|
||||||
|
output.normal = normalLength > 1e-8 ? normal / normalLength : float3(0, 0, 0);
|
||||||
|
output.color = vertex.color * instance.color;
|
||||||
|
output.material = instance.material.xy;
|
||||||
|
output.uv = vertex.uv;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
[shader("vertex")]
|
[shader("vertex")]
|
||||||
float4 gpuShadowMain(GpuSceneVertex vertex, uint drawInstance : SV_VulkanInstanceID) : SV_Position {
|
float4 gpuShadowMain(GpuSceneVertex vertex, uint drawInstance : SV_VulkanInstanceID) : SV_Position {
|
||||||
InstanceRecord instance = gfxInstances[gfxVisibleIds[gpuFrame.drawInfo.x + drawInstance]];
|
InstanceRecord instance = gfxInstances[gfxVisibleIds[gpuFrame.drawInfo.x + drawInstance]];
|
||||||
|
|||||||
@@ -130,10 +130,8 @@ void temporalResolveMain(uint3 dispatchId : SV_DispatchThreadID) {
|
|||||||
[[vk::binding(0,0)]] Texture2D<float4> resolvedHistoryColor;
|
[[vk::binding(0,0)]] Texture2D<float4> resolvedHistoryColor;
|
||||||
|
|
||||||
[shader("vertex")]
|
[shader("vertex")]
|
||||||
float4 temporalCompositeVertexMain(uint vertexId : SV_VertexID) : SV_Position {
|
float4 temporalCompositeVertexMain(float4 clip : POSITION) : SV_Position {
|
||||||
const float2 position = vertexId == 0 ? float2(-1, -1)
|
return clip;
|
||||||
: vertexId == 1 ? float2(3, -1) : float2(-1, 3);
|
|
||||||
return float4(position, 0, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[shader("fragment")]
|
[shader("fragment")]
|
||||||
|
|||||||
+719
-57
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,9 @@ void locations(const Json& fields, std::initializer_list<const char*> types, con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
void validate_layout(const Json& layout, std::string_view entry) {
|
void validate_layout(const Json& layout, std::string_view entry) {
|
||||||
const bool fragment = entry == "fragmentMain";
|
const bool fragment = entry == "fragmentMain" || entry == "temporalFragmentMain";
|
||||||
|
const bool temporal = entry == "temporalVertexMain" ||
|
||||||
|
entry == "temporalFragmentMain";
|
||||||
require(layout.at("stage") == (fragment ? "fragment" : "vertex"), "shader stage changed");
|
require(layout.at("stage") == (fragment ? "fragment" : "vertex"), "shader stage changed");
|
||||||
const auto& descriptors = layout.at("descriptors");
|
const auto& descriptors = layout.at("descriptors");
|
||||||
require(descriptors.is_array() && descriptors.size() == 8, "descriptor count changed");
|
require(descriptors.is_array() && descriptors.size() == 8, "descriptor count changed");
|
||||||
@@ -77,24 +79,42 @@ void validate_layout(const Json& layout, std::string_view entry) {
|
|||||||
"SPIR-V matrix storage convention changed");
|
"SPIR-V matrix storage convention changed");
|
||||||
}
|
}
|
||||||
if (fragment) {
|
if (fragment) {
|
||||||
locations(layout.at("inputs"),
|
if (temporal) {
|
||||||
{"float32x3", "float32x3", "float32x4", "float32x2", "float32x2"},
|
locations(layout.at("inputs"),
|
||||||
"fragment inputs");
|
{"float32x3", "float32x3", "float32x4", "float32x2", "float32x2",
|
||||||
locations(layout.at("outputs"), {"float32x4"}, "fragment outputs");
|
"float32x4", "float32x4", "float32"}, "temporal fragment inputs");
|
||||||
|
locations(layout.at("outputs"), {"float32x4", "float32x4"},
|
||||||
|
"temporal fragment outputs");
|
||||||
|
} else {
|
||||||
|
locations(layout.at("inputs"),
|
||||||
|
{"float32x3", "float32x3", "float32x4", "float32x2", "float32x2"},
|
||||||
|
"fragment inputs");
|
||||||
|
locations(layout.at("outputs"), {"float32x4"}, "fragment outputs");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
locations(layout.at("inputs"),
|
if (temporal) {
|
||||||
{"float32x4", "float32x3", "float32x3", "float32x4", "float32x2", "float32x2"},
|
locations(layout.at("inputs"),
|
||||||
"vertex inputs");
|
{"float32x4", "float32x3", "float32x3", "float32x4", "float32x2",
|
||||||
|
"float32x2", "float32x4", "float32"}, "temporal vertex inputs");
|
||||||
|
locations(layout.at("outputs"),
|
||||||
|
{"float32x3", "float32x3", "float32x4", "float32x2", "float32x2",
|
||||||
|
"float32x4", "float32x4", "float32"}, "temporal vertex outputs");
|
||||||
|
} else {
|
||||||
|
locations(layout.at("inputs"),
|
||||||
|
{"float32x4", "float32x3", "float32x3", "float32x4", "float32x2", "float32x2"},
|
||||||
|
"vertex inputs");
|
||||||
if (entry == "vertexMain")
|
if (entry == "vertexMain")
|
||||||
locations(layout.at("outputs"),
|
locations(layout.at("outputs"),
|
||||||
{"float32x3", "float32x3", "float32x4", "float32x2", "float32x2"},
|
{"float32x3", "float32x3", "float32x4", "float32x2", "float32x2"},
|
||||||
"vertex outputs");
|
"vertex outputs");
|
||||||
else
|
else
|
||||||
locations(layout.at("outputs"), {}, "shadow outputs");
|
locations(layout.at("outputs"), {}, "shadow outputs");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void validate_gpu_layout(const Json& layout, std::string_view entry) {
|
void validate_gpu_layout(const Json& layout, std::string_view entry) {
|
||||||
const bool graphics = entry == "gpuVertexMain" || entry == "gpuShadowMain";
|
const bool graphics = entry == "gpuVertexMain" || entry == "gpuShadowMain" ||
|
||||||
|
entry == "gpuTemporalVertexMain";
|
||||||
const bool hzb = entry == "gpuHzbMain";
|
const bool hzb = entry == "gpuHzbMain";
|
||||||
const bool compute = !graphics;
|
const bool compute = !graphics;
|
||||||
require(layout.at("stage") == (compute ? "compute" : "vertex"), "GPU shader stage changed");
|
require(layout.at("stage") == (compute ? "compute" : "vertex"), "GPU shader stage changed");
|
||||||
@@ -102,8 +122,8 @@ void validate_gpu_layout(const Json& layout, std::string_view entry) {
|
|||||||
const std::size_t expected_count = graphics ? 3 : hzb ? 2 : 10;
|
const std::size_t expected_count = graphics ? 3 : hzb ? 2 : 10;
|
||||||
require(descriptors.is_array() && descriptors.size() == expected_count,
|
require(descriptors.is_array() && descriptors.size() == expected_count,
|
||||||
"GPU descriptor count changed");
|
"GPU descriptor count changed");
|
||||||
const std::array<int, 10> compute_strides{224, 16, 16, 4, 16, 4, 4, 0, 0, 208};
|
const std::array<int, 10> compute_strides{288, 16, 16, 4, 16, 4, 4, 0, 0, 208};
|
||||||
const std::array<int, 3> graphics_strides{224, 4, 208};
|
const std::array<int, 3> graphics_strides{288, 4, 208};
|
||||||
for (std::size_t i = 0; i < expected_count; ++i) {
|
for (std::size_t i = 0; i < expected_count; ++i) {
|
||||||
const auto& binding = descriptors[i];
|
const auto& binding = descriptors[i];
|
||||||
require(binding.at("set") == (graphics ? 2 : 0) && binding.at("binding") == i &&
|
require(binding.at("set") == (graphics ? 2 : 0) && binding.at("binding") == i &&
|
||||||
@@ -147,7 +167,13 @@ void validate_gpu_layout(const Json& layout, std::string_view entry) {
|
|||||||
locations(layout.at("inputs"),
|
locations(layout.at("inputs"),
|
||||||
{"float32x3", "float32x3", "float32x4", "float32x2"},
|
{"float32x3", "float32x3", "float32x4", "float32x2"},
|
||||||
"GPU vertex inputs");
|
"GPU vertex inputs");
|
||||||
if (entry == "gpuVertexMain")
|
if (entry == "gpuVertexMain" || entry == "gpuTemporalVertexMain")
|
||||||
|
if (entry == "gpuTemporalVertexMain")
|
||||||
|
locations(layout.at("outputs"),
|
||||||
|
{"float32x3", "float32x3", "float32x4", "float32x2", "float32x2",
|
||||||
|
"float32x4", "float32x4", "float32"},
|
||||||
|
"GPU temporal vertex outputs");
|
||||||
|
else
|
||||||
locations(layout.at("outputs"),
|
locations(layout.at("outputs"),
|
||||||
{"float32x3", "float32x3", "float32x4", "float32x2", "float32x2"},
|
{"float32x3", "float32x3", "float32x4", "float32x2", "float32x2"},
|
||||||
"GPU vertex outputs");
|
"GPU vertex outputs");
|
||||||
@@ -205,7 +231,7 @@ void validate_temporal_layout(const Json& layout, std::string_view entry) {
|
|||||||
locations(layout.at("inputs"), {}, "temporal resolve inputs");
|
locations(layout.at("inputs"), {}, "temporal resolve inputs");
|
||||||
locations(layout.at("outputs"), {}, "temporal resolve outputs");
|
locations(layout.at("outputs"), {}, "temporal resolve outputs");
|
||||||
} else if (vertex) {
|
} else if (vertex) {
|
||||||
locations(layout.at("inputs"), {}, "temporal composite vertex inputs");
|
locations(layout.at("inputs"), {"float32x4"}, "temporal composite vertex inputs");
|
||||||
locations(layout.at("outputs"), {}, "temporal composite vertex outputs");
|
locations(layout.at("outputs"), {}, "temporal composite vertex outputs");
|
||||||
} else {
|
} else {
|
||||||
locations(layout.at("inputs"), {}, "temporal composite fragment inputs");
|
locations(layout.at("inputs"), {}, "temporal composite fragment inputs");
|
||||||
@@ -213,12 +239,14 @@ void validate_temporal_layout(const Json& layout, std::string_view entry) {
|
|||||||
"temporal composite fragment outputs");
|
"temporal composite fragment outputs");
|
||||||
}
|
}
|
||||||
const auto& input_builtins = layout.at("input_builtins");
|
const auto& input_builtins = layout.at("input_builtins");
|
||||||
require(input_builtins.is_array() && input_builtins.size() == 1 &&
|
require(input_builtins.is_array() && input_builtins.size() == (vertex ? 0u : 1u),
|
||||||
input_builtins[0].at("semantic") ==
|
"temporal entry input builtin count changed");
|
||||||
(resolve ? "SV_DISPATCHTHREADID" : vertex ? "SV_VERTEXID" : "SV_POSITION") &&
|
if (!vertex)
|
||||||
input_builtins[0].at("type") == (vertex ? "uint32" : resolve ? "uint32x3"
|
require(input_builtins[0].at("semantic") ==
|
||||||
: "float32x4"),
|
(resolve ? "SV_DISPATCHTHREADID" : "SV_POSITION") &&
|
||||||
"temporal entry input builtin changed");
|
input_builtins[0].at("type") ==
|
||||||
|
(resolve ? "uint32x3" : "float32x4"),
|
||||||
|
"temporal entry input builtin changed");
|
||||||
const auto& output_builtins = layout.at("output_builtins");
|
const auto& output_builtins = layout.at("output_builtins");
|
||||||
require(output_builtins.is_array() && output_builtins.size() == (vertex ? 1u : 0u),
|
require(output_builtins.is_array() && output_builtins.size() == (vertex ? 1u : 0u),
|
||||||
"temporal entry output builtin count changed");
|
"temporal entry output builtin count changed");
|
||||||
@@ -279,8 +307,10 @@ detail::ShaderCode load(const std::filesystem::path& directory, const char* entr
|
|||||||
? (std::string_view(entry) == "temporalResolveMain" ? 5u
|
? (std::string_view(entry) == "temporalResolveMain" ? 5u
|
||||||
: std::string_view(entry) == "temporalCompositeVertexMain" ? 0u : 4u)
|
: std::string_view(entry) == "temporalCompositeVertexMain" ? 0u : 4u)
|
||||||
: gpu ? ((std::string_view(entry) == "gpuVertexMain" ||
|
: gpu ? ((std::string_view(entry) == "gpuVertexMain" ||
|
||||||
std::string_view(entry) == "gpuShadowMain") ? 0u : 5u)
|
std::string_view(entry) == "gpuShadowMain" ||
|
||||||
: (std::string_view(entry) == "fragmentMain" ? 4u : 0u);
|
std::string_view(entry) == "gpuTemporalVertexMain") ? 0u : 5u)
|
||||||
|
: ((std::string_view(entry) == "fragmentMain" ||
|
||||||
|
std::string_view(entry) == "temporalFragmentMain") ? 4u : 0u);
|
||||||
validate_spirv(result.words, stage);
|
validate_spirv(result.words, stage);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -302,8 +332,16 @@ detail::load_temporal_shader_bundle(const std::filesystem::path& directory) {
|
|||||||
load(directory, "temporalCompositeVertexMain", false, true),
|
load(directory, "temporalCompositeVertexMain", false, true),
|
||||||
load(directory, "temporalCompositeFragmentMain", false, true)};
|
load(directory, "temporalCompositeFragmentMain", false, true)};
|
||||||
}
|
}
|
||||||
|
std::array<detail::ShaderCode, 3>
|
||||||
|
detail::load_temporal_scene_shader_bundle(const std::filesystem::path& directory) {
|
||||||
|
return {load(directory, "temporalVertexMain"),
|
||||||
|
load(directory, "temporalFragmentMain"),
|
||||||
|
load(directory, "gpuTemporalVertexMain", true)};
|
||||||
|
}
|
||||||
void validate_shader_bundle(const std::filesystem::path& directory) {
|
void validate_shader_bundle(const std::filesystem::path& directory) {
|
||||||
(void)detail::load_shader_bundle(directory);
|
(void)detail::load_shader_bundle(directory);
|
||||||
|
(void)detail::load_temporal_shader_bundle(directory);
|
||||||
|
(void)detail::load_temporal_scene_shader_bundle(directory);
|
||||||
}
|
}
|
||||||
void validate_gpu_shader_bundle(const std::filesystem::path& directory) {
|
void validate_gpu_shader_bundle(const std::filesystem::path& directory) {
|
||||||
(void)detail::load_gpu_shader_bundle(directory);
|
(void)detail::load_gpu_shader_bundle(directory);
|
||||||
|
|||||||
@@ -15,4 +15,7 @@ std::array<ShaderCode, 3> load_shader_bundle(const std::filesystem::path& direct
|
|||||||
std::array<ShaderCode, 5> load_gpu_shader_bundle(const std::filesystem::path& directory);
|
std::array<ShaderCode, 5> load_gpu_shader_bundle(const std::filesystem::path& directory);
|
||||||
// Order: temporal resolve compute, full-screen composite vertex and fragment.
|
// Order: temporal resolve compute, full-screen composite vertex and fragment.
|
||||||
std::array<ShaderCode, 3> load_temporal_shader_bundle(const std::filesystem::path& directory);
|
std::array<ShaderCode, 3> load_temporal_shader_bundle(const std::filesystem::path& directory);
|
||||||
|
// Direct temporal vertex/fragment and GPU temporal vertex; set 0 material,
|
||||||
|
// set 1 lighting and set 2 GPU scene stay at their established bindings.
|
||||||
|
std::array<ShaderCode, 3> load_temporal_scene_shader_bundle(const std::filesystem::path& directory);
|
||||||
} // namespace faset::render::detail
|
} // namespace faset::render::detail
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class ReflectionTests(unittest.TestCase):
|
|||||||
(d["set"], d["binding"]): d["element_stride"]
|
(d["set"], d["binding"]): d["element_stride"]
|
||||||
for d in gpu_vertex["layout"]["descriptors"]
|
for d in gpu_vertex["layout"]["descriptors"]
|
||||||
}
|
}
|
||||||
self.assertEqual([graphics[2, i] for i in range(3)], [224, 4, 208])
|
self.assertEqual([graphics[2, i] for i in range(3)], [288, 4, 208])
|
||||||
|
|
||||||
def test_gpu_vertex_paths_do_not_require_shader_draw_parameters(self):
|
def test_gpu_vertex_paths_do_not_require_shader_draw_parameters(self):
|
||||||
# SV_InstanceID makes Slang subtract BaseInstance and emit DrawParameters.
|
# SV_InstanceID makes Slang subtract BaseInstance and emit DrawParameters.
|
||||||
@@ -92,9 +92,31 @@ class ReflectionTests(unittest.TestCase):
|
|||||||
self.assertIn(1, capabilities) # Shader
|
self.assertIn(1, capabilities) # Shader
|
||||||
self.assertNotIn(4427, capabilities) # DrawParameters
|
self.assertNotIn(4427, capabilities) # DrawParameters
|
||||||
|
|
||||||
|
def test_temporal_composite_does_not_require_optional_draw_parameters(self):
|
||||||
|
compiler = os.environ["FASET_TEST_SLANGC"]
|
||||||
|
with tempfile.TemporaryDirectory(prefix="faset-temporal-composite-") as directory:
|
||||||
|
process = subprocess.run(
|
||||||
|
[sys.executable, str(SCRIPT), "--compiler", compiler, "--source",
|
||||||
|
str(SCRIPT.parents[1] / "shaders" / "temporal.slang"), "--entry",
|
||||||
|
"temporalCompositeVertexMain", "--define", "FASET_TEMPORAL_COMPOSITE=1",
|
||||||
|
"--output", directory], capture_output=True, text=True,
|
||||||
|
)
|
||||||
|
self.assertEqual(process.returncode, 0, process.stderr)
|
||||||
|
bytecode = (Path(directory) / "temporalCompositeVertexMain.spv").read_bytes()
|
||||||
|
words = struct.unpack(f"<{len(bytecode) // 4}I", bytecode)
|
||||||
|
capabilities = set()
|
||||||
|
offset = 5
|
||||||
|
while offset < len(words):
|
||||||
|
count, opcode = words[offset] >> 16, words[offset] & 0xffff
|
||||||
|
self.assertGreater(count, 0)
|
||||||
|
if opcode == 17:
|
||||||
|
capabilities.add(words[offset + 1])
|
||||||
|
offset += count
|
||||||
|
self.assertNotIn(4427, capabilities) # DrawParameters
|
||||||
|
|
||||||
def test_gpu_storage_resources_keep_kind_and_stride(self):
|
def test_gpu_storage_resources_keep_kind_and_stride(self):
|
||||||
parameters = [
|
parameters = [
|
||||||
parameter("instances", 0, "structuredBuffer", "read", 224),
|
parameter("instances", 0, "structuredBuffer", "read", 288),
|
||||||
parameter("visibleIds", 1, "structuredBuffer", "readWrite", 4),
|
parameter("visibleIds", 1, "structuredBuffer", "readWrite", 4),
|
||||||
parameter("depthOutput", 2, "texture2D", "readWrite"),
|
parameter("depthOutput", 2, "texture2D", "readWrite"),
|
||||||
parameter("depthInput", 3, "texture2D", "read"),
|
parameter("depthInput", 3, "texture2D", "read"),
|
||||||
@@ -111,7 +133,7 @@ class ReflectionTests(unittest.TestCase):
|
|||||||
descriptors = layout["descriptors"]
|
descriptors = layout["descriptors"]
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[(d["type"], d.get("element_stride")) for d in descriptors],
|
[(d["type"], d.get("element_stride")) for d in descriptors],
|
||||||
[("storage_buffer", 224), ("storage_buffer", 4),
|
[("storage_buffer", 288), ("storage_buffer", 4),
|
||||||
("storage_image_2d", None), ("sampled_image_2d", None)],
|
("storage_image_2d", None), ("sampled_image_2d", None)],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -128,7 +150,7 @@ class ReflectionTests(unittest.TestCase):
|
|||||||
metadata = json.loads((Path(directory) / "gpuCullMain.reflection.json").read_text())
|
metadata = json.loads((Path(directory) / "gpuCullMain.reflection.json").read_text())
|
||||||
bindings = {item["binding"]: item for item in metadata["layout"]["descriptors"]}
|
bindings = {item["binding"]: item for item in metadata["layout"]["descriptors"]}
|
||||||
self.assertEqual([bindings[n]["element_stride"] for n in (0, 1, 2, 3, 4, 5, 6, 9)],
|
self.assertEqual([bindings[n]["element_stride"] for n in (0, 1, 2, 3, 4, 5, 6, 9)],
|
||||||
[224, 16, 16, 4, 16, 4, 4, 208])
|
[288, 16, 16, 4, 16, 4, 4, 208])
|
||||||
self.assertEqual([bindings[n]["type"] for n in (7, 8)],
|
self.assertEqual([bindings[n]["type"] for n in (7, 8)],
|
||||||
["sampled_image_2d", "sampled_image_2d"])
|
["sampled_image_2d", "sampled_image_2d"])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user