[Vulkan] Route guest oDepth through gl_FragDepth in FBO and into FSI depth test
This commit is contained in:
@@ -145,6 +145,8 @@ void SpirvShaderTranslator::Reset() {
|
||||
var_main_fsi_color_written_ = spv::NoResult;
|
||||
std::fill(output_fragment_data_.begin(), output_fragment_data_.end(),
|
||||
spv::NoResult);
|
||||
output_or_var_fragment_depth_ = spv::NoResult;
|
||||
output_fragment_depth_ = spv::NoResult;
|
||||
|
||||
main_switch_op_.reset();
|
||||
main_switch_next_pc_phi_operands_.clear();
|
||||
@@ -723,7 +725,7 @@ std::vector<uint8_t> SpirvShaderTranslator::CompleteTranslation() {
|
||||
builder_->addExecutionMode(function_main_,
|
||||
spv::ExecutionModeEarlyFragmentTests);
|
||||
}
|
||||
if (current_shader().writes_depth()) {
|
||||
if (current_shader().writes_depth() && !edram_fragment_shader_interlock_) {
|
||||
builder_->addExecutionMode(function_main_,
|
||||
spv::ExecutionModeDepthReplacing);
|
||||
}
|
||||
@@ -2285,6 +2287,19 @@ void SpirvShaderTranslator::StartFragmentShaderBeforeMain() {
|
||||
}
|
||||
}
|
||||
|
||||
// Fragment depth output (gl_FragDepth) for the FBO path.
|
||||
// Created when the guest pixel shader writes oDepth.
|
||||
// FSI manages its own depth and does not need an Output.
|
||||
if (!edram_fragment_shader_interlock_ && !is_depth_only_fragment_shader_ &&
|
||||
current_shader().writes_depth()) {
|
||||
output_fragment_depth_ = builder_->createVariable(
|
||||
spv::NoPrecision, spv::StorageClassOutput, type_float_, "gl_FragDepth");
|
||||
builder_->addDecoration(output_fragment_depth_, spv::DecorationBuiltIn,
|
||||
static_cast<int>(spv::BuiltIn::FragDepth));
|
||||
builder_->addDecoration(output_fragment_depth_, spv::DecorationInvariant);
|
||||
main_interface_.push_back(output_fragment_depth_);
|
||||
}
|
||||
|
||||
// Sample mask output for alpha-to-coverage.
|
||||
// Only needed for non-FSI mode. FSI uses main_fsi_sample_mask_ instead.
|
||||
output_fragment_sample_mask_ = spv::NoResult;
|
||||
@@ -2361,14 +2376,14 @@ void SpirvShaderTranslator::StartFragmentShaderInMain() {
|
||||
"xe_var_color_written", const_uint_0_);
|
||||
}
|
||||
|
||||
if (edram_fragment_shader_interlock_) {
|
||||
// Initialize depth output variable with fragment shader interlock.
|
||||
output_or_var_fragment_depth_ = spv::NoResult;
|
||||
if (current_shader().writes_depth()) {
|
||||
output_or_var_fragment_depth_ = builder_->createVariable(
|
||||
spv::NoPrecision, spv::StorageClassFunction, type_float_,
|
||||
"xe_var_fragment_depth", const_float_0_);
|
||||
}
|
||||
// Staging variable for guest oDepth writes.
|
||||
// Created whenever the shader uses oDepth:
|
||||
// * FSI reads it during its EDRAM depth write inside the interlock.
|
||||
// * FBO copies it to gl_FragDepth at the end of the shader.
|
||||
if (current_shader().writes_depth()) {
|
||||
output_or_var_fragment_depth_ = builder_->createVariable(
|
||||
spv::NoPrecision, spv::StorageClassFunction, type_float_,
|
||||
"xe_var_fragment_depth", const_float_0_);
|
||||
}
|
||||
|
||||
if (edram_fragment_shader_interlock_ && FSI_IsDepthStencilEarly()) {
|
||||
@@ -2974,6 +2989,26 @@ void SpirvShaderTranslator::StoreResult(const InstructionResult& result,
|
||||
var_main_memexport_data_written_);
|
||||
}
|
||||
} break;
|
||||
case InstructionStorageTarget::kDepth: {
|
||||
// oDepth is scalar. The FBO path copies it to gl_FragDepth (in
|
||||
// CompleteFragmentShader_DSV_DepthTo24Bit), while FSI writes it to a
|
||||
// depth variable consumed by FSI_DepthStencilTest.
|
||||
assert_true(is_pixel_shader());
|
||||
assert_true(used_write_mask == 0b0001);
|
||||
assert_true(current_shader().writes_depth());
|
||||
assert_true(output_or_var_fragment_depth_ != spv::NoResult);
|
||||
target_pointer = output_or_var_fragment_depth_;
|
||||
// Depth outside [0, 1] needs to be clamped for safety, similar to D3D12.
|
||||
// Though 20e4 float depth can store values between 1 and 2, it's a very
|
||||
// unusual case. In Vulkan, gl_FragDepth can accept any values when the
|
||||
// depth buffer is floating-point, but we clamp for consistency.
|
||||
// Clamp the depth value to [0, 1] if not already saturated.
|
||||
if (value != spv::NoResult && !result.is_clamped) {
|
||||
value = builder_->createTriBuiltinCall(
|
||||
type_float_, ext_inst_glsl_std_450_, GLSLstd450NClamp, value,
|
||||
const_float_0_, const_float_1_);
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
// TODO(Triang3l): All storage targets.
|
||||
break;
|
||||
|
||||
@@ -563,6 +563,11 @@ class SpirvShaderTranslator : public ShaderTranslator {
|
||||
void StartFragmentShaderInMain();
|
||||
void CompleteFragmentShaderInMain();
|
||||
|
||||
// Writes gl_FragDepth at the end of an FBO pixel shader, remapping the
|
||||
// guest 0...1 oDepth value to host 0...0.5 when the bound depth buffer is
|
||||
// float24. No-op for FSI and for shaders that don't write oDepth.
|
||||
void CompleteFragmentShader_DSV_DepthTo24Bit();
|
||||
|
||||
// Updates the current flow control condition (to be called in the beginning
|
||||
// of exec and in jumps), closing the previous conditionals if needed.
|
||||
// However, if the condition is not different, the instruction-level predicate
|
||||
@@ -960,11 +965,16 @@ class SpirvShaderTranslator : public ShaderTranslator {
|
||||
// output_or_var_fragment_data_.
|
||||
std::array<spv::Id, xenos::kMaxColorRenderTargets> output_fragment_data_;
|
||||
|
||||
// Fragment shader depth output (gl_FragDepth).
|
||||
// With fragment shader interlock, a variable in the main function.
|
||||
// Otherwise, the depth output (only created if shader writes depth).
|
||||
// Function-scoped staging variable for guest oDepth writes. Used by both
|
||||
// FSI (which writes the value to the EDRAM buffer inside the interlock)
|
||||
// and FBO (copied to output_fragment_depth_ at the end of the shader,
|
||||
// remapping guest 0...1 to host 0...0.5 when the depth format is float24).
|
||||
spv::Id output_or_var_fragment_depth_;
|
||||
|
||||
// FBO only: actual gl_FragDepth Output.
|
||||
// Written at the end of the pixel shader from output_or_var_fragment_depth_.
|
||||
spv::Id output_fragment_depth_;
|
||||
|
||||
// Fragment shader sample mask output (gl_SampleMask).
|
||||
// Only used for alpha-to-coverage in non-FSI mode.
|
||||
// For FSI mode, sample mask is handled via main_fsi_sample_mask_.
|
||||
|
||||
@@ -1483,6 +1483,10 @@ void SpirvShaderTranslator::CompleteFragmentShaderInMain() {
|
||||
}
|
||||
}
|
||||
|
||||
// Copies the staged depth to the FBO gl_FragDepth output.
|
||||
// No-op for FSI and when no host depth output was declared.
|
||||
CompleteFragmentShader_DSV_DepthTo24Bit();
|
||||
|
||||
if (edram_fragment_shader_interlock_) {
|
||||
if (block_fsi_if_after_depth_stencil_merge) {
|
||||
builder_->createBranch(block_fsi_if_after_depth_stencil_merge);
|
||||
@@ -1503,6 +1507,37 @@ void SpirvShaderTranslator::CompleteFragmentShaderInMain() {
|
||||
}
|
||||
}
|
||||
|
||||
void SpirvShaderTranslator::CompleteFragmentShader_DSV_DepthTo24Bit() {
|
||||
// FSI manages its own depth via the EDRAM buffer - this hook is FBO-only.
|
||||
// Likewise, if no Output FragDepth was declared, there is nothing to write.
|
||||
if (edram_fragment_shader_interlock_ ||
|
||||
output_fragment_depth_ == spv::NoResult) {
|
||||
return;
|
||||
}
|
||||
if (!current_shader().writes_depth()) {
|
||||
return;
|
||||
}
|
||||
assert_true(output_or_var_fragment_depth_ != spv::NoResult);
|
||||
// The shader writes depth explicitly, for float24, need to scale it from
|
||||
// guest 0...1 to host 0...0.5 to support reinterpretation round trips as
|
||||
// viewport scaling doesn't apply to oDepth.
|
||||
spv::Id depth_value =
|
||||
builder_->createLoad(output_or_var_fragment_depth_, spv::NoPrecision);
|
||||
spv::Id depth_float24_flag = builder_->createBinOp(
|
||||
spv::OpINotEqual, type_bool_,
|
||||
builder_->createBinOp(spv::OpBitwiseAnd, type_uint_,
|
||||
main_system_constant_flags_,
|
||||
builder_->makeUintConstant(kSysFlag_DepthFloat24)),
|
||||
const_uint_0_);
|
||||
spv::Id depth_scaled = builder_->createBinOp(
|
||||
spv::OpFMul, type_float_, depth_value, builder_->makeFloatConstant(0.5f));
|
||||
spv::Id depth_remapped =
|
||||
builder_->createTriOp(spv::OpSelect, type_float_, depth_float24_flag,
|
||||
depth_scaled, depth_value);
|
||||
// Write the depth from the temporary to the system depth output.
|
||||
builder_->createStore(depth_remapped, output_fragment_depth_);
|
||||
}
|
||||
|
||||
spv::Id SpirvShaderTranslator::LoadMsaaSamplesFromFlags() {
|
||||
return builder_->createTriOp(
|
||||
spv::OpBitFieldUExtract, type_uint_, main_system_constant_flags_,
|
||||
@@ -1798,21 +1833,35 @@ void SpirvShaderTranslator::FSI_DepthStencilTest(
|
||||
SpirvBuilder::IfBuilder if_depth_stencil_enabled(
|
||||
depth_stencil_enabled, spv::SelectionControlDontFlattenMask, *builder_);
|
||||
|
||||
// Load the depth in the center of the pixel and calculate the derivatives of
|
||||
// the depth outside non-uniform control flow.
|
||||
assert_true(input_fragment_coordinates_ != spv::NoResult);
|
||||
id_vector_temp_.clear();
|
||||
id_vector_temp_.push_back(builder_->makeIntConstant(2));
|
||||
spv::Id center_depth32_unbiased = builder_->createLoad(
|
||||
builder_->createAccessChain(spv::StorageClassInput,
|
||||
input_fragment_coordinates_, id_vector_temp_),
|
||||
spv::NoPrecision);
|
||||
builder_->addCapability(spv::CapabilityDerivativeControl);
|
||||
spv::Id center_depth32_unbiased;
|
||||
std::array<spv::Id, 2> depth_dxy;
|
||||
depth_dxy[0] = builder_->createUnaryOp(spv::OpDPdxCoarse, type_float_,
|
||||
center_depth32_unbiased);
|
||||
depth_dxy[1] = builder_->createUnaryOp(spv::OpDPdyCoarse, type_float_,
|
||||
center_depth32_unbiased);
|
||||
|
||||
// Guest oDepth replaces the depth value FSI tests. It's not the raster depth
|
||||
// plane, so don't take FragCoord for it.
|
||||
if (current_shader().writes_depth()) {
|
||||
assert_false(is_early);
|
||||
assert_true(output_or_var_fragment_depth_ != spv::NoResult);
|
||||
center_depth32_unbiased =
|
||||
builder_->createLoad(output_or_var_fragment_depth_, spv::NoPrecision);
|
||||
depth_dxy[0] = const_float_0_;
|
||||
depth_dxy[1] = const_float_0_;
|
||||
} else {
|
||||
// Load the depth in the center of the pixel and calculate the derivatives
|
||||
// of the depth outside non-uniform control flow.
|
||||
assert_true(input_fragment_coordinates_ != spv::NoResult);
|
||||
id_vector_temp_.clear();
|
||||
id_vector_temp_.push_back(builder_->makeIntConstant(2));
|
||||
center_depth32_unbiased =
|
||||
builder_->createLoad(builder_->createAccessChain(
|
||||
spv::StorageClassInput,
|
||||
input_fragment_coordinates_, id_vector_temp_),
|
||||
spv::NoPrecision);
|
||||
builder_->addCapability(spv::CapabilityDerivativeControl);
|
||||
depth_dxy[0] = builder_->createUnaryOp(spv::OpDPdxCoarse, type_float_,
|
||||
center_depth32_unbiased);
|
||||
depth_dxy[1] = builder_->createUnaryOp(spv::OpDPdyCoarse, type_float_,
|
||||
center_depth32_unbiased);
|
||||
}
|
||||
|
||||
// Skip everything if potentially discarded all the samples previously in the
|
||||
// shader.
|
||||
@@ -1981,27 +2030,36 @@ void SpirvShaderTranslator::FSI_DepthStencilTest(
|
||||
builder_->makeUintConstant(uint32_t(1) << 2)),
|
||||
const_uint_0_);
|
||||
|
||||
// Get the maximum depth slope for the polygon offset.
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/direct3d9/depth-bias
|
||||
std::array<spv::Id, 2> depth_dxy_abs;
|
||||
for (uint32_t i = 0; i < 2; ++i) {
|
||||
depth_dxy_abs[i] = builder_->createUnaryBuiltinCall(
|
||||
type_float_, ext_inst_glsl_std_450_, GLSLstd450FAbs, depth_dxy[i]);
|
||||
spv::Id center_depth32_biased;
|
||||
|
||||
// When the guest shader replaces depth, don't apply offset from the original
|
||||
// FragCoord.z plane. That would mix two different depth sources.
|
||||
if (current_shader().writes_depth()) {
|
||||
center_depth32_biased = center_depth32_unbiased;
|
||||
} else {
|
||||
// Get the maximum depth slope for the polygon offset.
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/direct3d9/depth-bias
|
||||
std::array<spv::Id, 2> depth_dxy_abs;
|
||||
for (uint32_t i = 0; i < 2; ++i) {
|
||||
depth_dxy_abs[i] = builder_->createUnaryBuiltinCall(
|
||||
type_float_, ext_inst_glsl_std_450_, GLSLstd450FAbs, depth_dxy[i]);
|
||||
}
|
||||
spv::Id depth_max_slope = builder_->createBinBuiltinCall(
|
||||
type_float_, ext_inst_glsl_std_450_, GLSLstd450FMax, depth_dxy_abs[0],
|
||||
depth_dxy_abs[1]);
|
||||
// Calculate the polygon offset.
|
||||
spv::Id slope_scaled_poly_offset = builder_->createNoContractionBinOp(
|
||||
spv::OpFMul, type_float_, poly_offset_scale, depth_max_slope);
|
||||
spv::Id poly_offset = builder_->createNoContractionBinOp(
|
||||
spv::OpFAdd, type_float_, slope_scaled_poly_offset, poly_offset_offset);
|
||||
// Apply the post-clip and post-viewport polygon offset to the fragment's
|
||||
// depth. Not clamping yet as this is at the center, which is not
|
||||
// necessarily covered and not necessarily inside the bounds - derivatives
|
||||
// scaled by sample locations will be added to this value, and it must be
|
||||
// linear.
|
||||
center_depth32_biased = builder_->createNoContractionBinOp(
|
||||
spv::OpFAdd, type_float_, center_depth32_unbiased, poly_offset);
|
||||
}
|
||||
spv::Id depth_max_slope = builder_->createBinBuiltinCall(
|
||||
type_float_, ext_inst_glsl_std_450_, GLSLstd450FMax, depth_dxy_abs[0],
|
||||
depth_dxy_abs[1]);
|
||||
// Calculate the polygon offset.
|
||||
spv::Id slope_scaled_poly_offset = builder_->createNoContractionBinOp(
|
||||
spv::OpFMul, type_float_, poly_offset_scale, depth_max_slope);
|
||||
spv::Id poly_offset = builder_->createNoContractionBinOp(
|
||||
spv::OpFAdd, type_float_, slope_scaled_poly_offset, poly_offset_offset);
|
||||
// Apply the post-clip and post-viewport polygon offset to the fragment's
|
||||
// depth. Not clamping yet as this is at the center, which is not necessarily
|
||||
// covered and not necessarily inside the bounds - derivatives scaled by
|
||||
// sample locations will be added to this value, and it must be linear.
|
||||
spv::Id center_depth32_biased = builder_->createNoContractionBinOp(
|
||||
spv::OpFAdd, type_float_, center_depth32_unbiased, poly_offset);
|
||||
|
||||
// Perform depth and stencil testing for each covered sample.
|
||||
spv::Id new_sample_mask = main_fsi_sample_mask_;
|
||||
|
||||
Reference in New Issue
Block a user