fix(agc): refuse depth bounds outside [0, 1] without VK_EXT_depth_range_unrestricted

Clamping them changed the result where the stored depth is exactly 0 or 1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michon 2026-10-03 10:40:47 +02:00
parent c15238ddc0
commit 057bcb1b42
4 changed files with 16 additions and 2 deletions

View File

@ -83,6 +83,7 @@ std::shared_ptr<Pipeline> CachedPipeline(const Context& context, const State& st
void ClearCachedPipelines(VkDevice device);
// Device limit checks of the viewport, which is dynamic state and so no longer checked by Pipeline.
void ValidateViewport(const Context& context, const VkViewport& viewport);
void ValidateDepthBounds(const Context& context, const State& state);
void ValidateShaderPair(const ShaderRecompiler::RecompileResult& vertex, const ShaderRecompiler::RecompileResult& fragment);
// Returns the color attachment locations the pixel shader writes.

View File

@ -778,6 +778,7 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P
if (state.stages.tessellation) Require(draw.indexCount % state.stages.tessellation->inputControlPoints == 0, "incomplete tessellation patch");
// Viewport and scissor are dynamic pipeline state, so their limits are checked here per draw.
ValidateViewport(context, state.viewport);
ValidateDepthBounds(context, state);
timer.phase(PhaseValidate);
inputs.maxIndex = draw.indexed ? 0u : draw.firstVertex + draw.indexCount - 1u;
if (draw.indexed) {

View File

@ -32,6 +32,10 @@ Framebuffer::~Framebuffer() {
if (framebuffer) context.Function<PFN_vkDestroyFramebuffer>("vkDestroyFramebuffer")(context.device, framebuffer, nullptr);
}
void ValidateDepthBounds(const Context& context, const State& state) {
Require(!state.depthBoundsTest || context.depthRangeUnrestricted || (state.minDepthBounds >= 0.0f && state.minDepthBounds <= 1.0f && state.maxDepthBounds >= 0.0f && state.maxDepthBounds <= 1.0f), "depth bounds outside [0, 1] require VK_EXT_depth_range_unrestricted");
}
void ValidateViewport(const Context& context, const VkViewport& viewport) {
Require(std::isfinite(viewport.minDepth) && std::isfinite(viewport.maxDepth), "non-finite viewport depth range");
Require(context.depthRangeUnrestricted || (viewport.minDepth >= 0 && viewport.minDepth <= 1 && viewport.maxDepth >= 0 && viewport.maxDepth <= 1), "viewport depth outside [0, 1] requires VK_EXT_depth_range_unrestricted");
@ -285,8 +289,7 @@ void Pipeline::Continue(VkCommandBuffer commands, const State& state) const {
context.Resolved(&DeviceFunctions::cmdSetScissor, "vkCmdSetScissor")(commands, 0, 1, &state.scissor);
if (depthBias) context.Resolved(&DeviceFunctions::cmdSetDepthBias, "vkCmdSetDepthBias")(commands, state.depthBiasConstant, state.depthBiasClamp, state.depthBiasSlope);
if (!depthBounds) return;
const auto bound = [&](float value) { return context.depthRangeUnrestricted ? value : std::clamp(value, 0.0f, 1.0f); };
context.Resolved(&DeviceFunctions::cmdSetDepthBounds, "vkCmdSetDepthBounds")(commands, bound(state.minDepthBounds), bound(state.maxDepthBounds));
context.Resolved(&DeviceFunctions::cmdSetDepthBounds, "vkCmdSetDepthBounds")(commands, state.minDepthBounds, state.maxDepthBounds);
}
void Pipeline::PushConstants(VkCommandBuffer commands, VkShaderStageFlags stages, std::span<const std::byte, PipelinePushConstantBytes> bytes) const {

View File

@ -1702,6 +1702,15 @@ int main() {
draw.indexAddress = 0;
draw.flags = 1;
expectFailure([&] { AgcDriver::Graphics::Draw(context, state, draw, {}); }, "draw modifiers");
auto bounded = state;
bounded.depthBoundsTest = true;
bounded.minDepthBounds = 0.25f;
AgcDriver::Graphics::ValidateDepthBounds(context, bounded);
bounded.minDepthBounds = 1.5f;
expectFailure([&] { AgcDriver::Graphics::ValidateDepthBounds(context, bounded); }, "depth bounds outside [0, 1]");
auto unrestricted = context;
unrestricted.depthRangeUnrestricted = true;
AgcDriver::Graphics::ValidateDepthBounds(unrestricted, bounded);
}
stateTests();
hardwareScreenOffsetTests();