Repository navigation
Conversation
Allow dynamic-rendering pipelines with no color attachment when a depth or stencil format is set, make the fragment stage optional for them, and add configurable depth bias plus VulkanDynamicRendering::BeginDepthRendering. CarControlDemo uses this to render the car into a shadow map and shade the ground with comparison-sampled shadows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
VulkanRendercould not create a graphics pipeline without a color attachment. On the dynamic-rendering path it threw"Dynamic rendering requires at least one color format". A fragment shader was always required. Depth bias was hard-coded toVK_FALSE.VulkanDynamicRenderingalways started rendering with exactly one color attachment and aDONT_CAREdepth store.As a result, the library could not express a depth-only pass: no shadow maps, no depth prepass, no rendering into a depth image that is sampled later. The demos had no shadows at all, so the car in CarControlDemo floated over the ground.
Solution
Library (
vulkan/):VulkanRendercolorAttachmentCountis 0 and the color-blend state has no attachments.LoadFragmentShader(), which omits the fragment stage. A pipeline that has a color attachment still requires a fragment shader.SetDepthBias()returnsstd::optional<DepthBiasInfo{constant_factor, slope_factor}>. The clamp is fixed at 0, so thedepthBiasClampdevice feature is not needed. Non-finite factors are rejected.VulkanDynamicRendering::BeginDepthRendering(cmd, depth_view, extent, clear)starts rendering with no color attachments and a depth attachment that clears and then stores. It rejects a null command buffer, a null view, or an empty extent.Existing subclasses and callers keep their behaviour. The new virtual has a default, and every pipeline that has a color attachment builds the same state as before.
Consumer: CarControlDemo car shadows (
examples/vulkan/CarControlDemo/)RenderCarShadowis a depth-onlyVulkanRenderwith a vertex shader only, depth bias 1.25 constant / 1.75 slope, and the light matrix passed as a push constant. It owns a 2048²D32_SFLOATshadow map and a comparison sampler (LESS_OR_EQUAL, opaque-white border, linear filtering only if the format supports it). It reuses the car's existing vertex and index buffers through new read-only accessors onRenderCar.Record()does the following:UNDEFINEDto the depth-attachment layout; the source stage is the previous frame's fragment-shader reads.BeginDepthRendering.SHADER_READ_ONLYfor the fragment shader.RenderGroundsamples the map throughsampler2DShadowwith 3×3 PCF and darkens shadowed asphalt.main.cppbuilds a light projection centred on the car withglm::orthoRH_ZO, using the same direction as the car's key light.Usage pattern for other callers:
DynamicRenderingInfo info{}; info.depth_format = VK_FORMAT_D32_SFLOAT; // subclass VulkanRender(context, info): LoadFragmentShader() returns {}, SetDepthBias() optional dynamic_rendering.BeginDepthRendering(cmd, depth_view, extent, clear); // bind pipeline, draw dynamic_rendering.EndDynamicRendering(cmd);Callers still own layout transitions and synchronization for the depth image, as they already do for every other image in this library.
Candidates considered
0xf; Adreno type 6 =0xf)AHardwareBufferimport into VulkanOverlap with open PRs: #137 adds one line in
VulkanRender.cpp(passing the pipeline cache) and #137/#141 touch small setup hunks in CarControlmain.cpp. Any conflicts should be small and in different places. No shared modules, other backends or new dependencies are touched.Validation
Final commit
2656b0b. Its tree,30d1136, is exactly the tree that was built and tested on both platforms (diff sha2567f3a0af3…). Both platforms were built from clean build directories. Base isorigin/master0dc12da, freshly fetched before validation and again before commit.macOS native (Apple M6 / MoltenVK, macOS 27.0.1, Xcode SDK MacOSX27.2, Vulkan SDK 1.4.350.0,
corevariant with-Wall -Wextra -Wpedantic -Werror):source /Users/joechu/VulkanSDK/1.4.350.0/setup-env.sh ./scripts/run.sh -t macos -r testsVK_LOADER_DEBUG=layer), and no validation messages appeared.vk_car_control_demoran several times with validation on and produced 0 messages. A frame captured on the 120th present (through a scratch present-hook shim, not part of this PR) shows the soft car shadow on the ground, offset toward −X/−Z as the light direction predicts. The capture was repeated after the review fix to the light projection.Android arm64-v8a physical device: Xiaomi 2210132C (nuwa, qcom / Adreno 740), Android 13 / API 33.
adb devices -llisted exactly one authorized device,1dda2807;ANDROID_SERIALwas pinned to it andro.kernel.qemuis empty (not an emulator). NDK 28.2.13676358 (r28c), target API 34.VulkanDepthOnlyRenderTestcases reportedOKon the device. Zero compiler warnings.VKDBGUTILWARN003; this is not a validation message.New tests (
tests/vulkan/VulkanDepthOnlyRenderTest.cpp): a 32×32D32_SFLOATimage, a pipeline with no color attachment and no fragment stage, and depth read back withvkCmdCopyImageToBuffer.BeginDepthRenderingwith a null view, null command buffer, or zero width or height.Review gate: independent GPT 6.1 sol, high effort, run read-only and ephemeral.
GLM_FORCE_DEPTH_ZERO_TO_ONE, so the light projection used a −1..1 depth range. Fixed with an explicitorthoRH_ZO.Limitations
Changes
git diff --numstat origin/master: +782 / −28 = 810 lines.vulkan/include/VulkanRender.h+7vulkan/src/VulkanRender.cpp+50 / −18vulkan/include/VulkanDynamicRendering.h+2vulkan/src/VulkanDynamicRendering.cpp+39RenderCarShadow.h/.cpp+240 (new)CarShadow.vert+11 (new)Ground.vert/.frag+22RenderGround.h/.cpp,RenderCar.h/.cpp,main.cpp: the remainderVulkanDepthOnlyRenderTest.cpp+347DepthOnly.vert+13Risk
std::invalid_argumentbefore any Vulkan objects are created. The error message for more than one color format changed wording ("at most one").D32image, about 16 MiB) and binds a third ground descriptor.RenderGround::InitandUpdateUniformBuffergained shadow parameters; their only caller is this demo.D32_SFLOATsupports depth attachment plus sampling and throws a clear error otherwise. The tests skip if the queue cannot record graphics work or ifD32_SFLOATcannot be copied.🤖 Generated with Claude Code