fix(render3d): Vulkan exposure, foliage depth and meadow alpha

Three things the first Vulkan frames on the RTX 3070 Ti showed against OpenGL on the same PC:

- Exposure: the adaptation pass reads the HDR scene's smallest mip, and a render target made
  without pixels had one level, so exposure came from a single texel. A target asked for mipmaps
  now grows a full chain (level 0 kept), and passes draw through level-0 views keyed by the
  image's generation.
- Foliage: the depth prepass and the lit pass (depth EQUAL) are different variants. Vulkan vertex
  stages now declare an invariant gl_Position so both land on the same depth.
- Alpha to coverage is enabled only on a multisampled pass. OpenGL ignores it without MSAA; Vulkan
  with one sample dropped every fragment under half alpha.

vk_resources also checks a big-endian 16-bit RGB upload (a normal map). VKRES OK; ludic-dev test
140 passed; the PC's Vulkan frame is validation-clean; OpenGL frames byte-identical at the five
viewpoints with 59 self-tests passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-15 12:29:24 +03:00
parent 072f9a848e
commit a674c9b372
33 changed files with 70 additions and 7 deletions

View file

@ -290,7 +290,9 @@ function gvk_pipeline(p: int, m: Mesh, st: GvkState, n_color: int, color_fmt: in
Vk.zero(ms, VkPipelineMultisampleStateCreateInfo_sizeof)
Vk.put_i32(ms, VkPipelineMultisampleStateCreateInfo_sType, VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO)
Vk.put_i32(ms, VkPipelineMultisampleStateCreateInfo_rasterizationSamples, samples)
Vk.put_i32(ms, VkPipelineMultisampleStateCreateInfo_alphaToCoverageEnable, st.a2c)
# OpenGL ignores alpha to coverage without a multisampled target; Vulkan with one sample would
# instead drop every fragment under half alpha, which erased the meadow's flowers
if samples > 1 { Vk.put_i32(ms, VkPipelineMultisampleStateCreateInfo_alphaToCoverageEnable, st.a2c) }
let ds = bytes(VkPipelineDepthStencilStateCreateInfo_sizeof)
Vk.zero(ds, VkPipelineDepthStencilStateCreateInfo_sizeof)
Vk.put_i32(ds, VkPipelineDepthStencilStateCreateInfo_sType, VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO)
@ -607,10 +609,12 @@ function gvk_frame_cb() -> pointer {
return gvk_cb
}
# a view of one layer of an array image, for a cascade drawn into on its own
# The view a pass draws into: level 0 only (an attachment view has exactly one level, and a target
# the exposure measure mipmaps has several), and one layer of an array image for a cascade drawn
# on its own. Keyed by the image's generation, so a replaced image never reuses a stale view.
function gvk_view_of(tex: int, layer1: int) -> long {
if layer1 == 0 { return gvk_tex_view[tex] }
let key = `{tex}:{layer1}`
if layer1 == 0 and gvk_tex_levels[tex] <= 1 and gvk_tex_layers[tex] <= 1 { return gvk_tex_view[tex] }
let key = `{tex}:{gvk_tex_gen[tex]}:{layer1}`
if gvk_layer_views == null { gvk_layer_views = new []string; gvk_layer_view = new []long }
for i in 0 .. len(gvk_layer_views) { if gvk_layer_views[i] == key { return gvk_layer_view[i] } }
let depth = gvk_tex_vkfmt[tex] == VK_FORMAT_D32_SFLOAT
@ -623,7 +627,7 @@ function gvk_view_of(tex: int, layer1: int) -> long {
let sr = VkImageViewCreateInfo_subresourceRange
if depth { Vk.put_i32(vci, sr + VkImageSubresourceRange_aspectMask, VK_IMAGE_ASPECT_DEPTH_BIT) } else { Vk.put_i32(vci, sr + VkImageSubresourceRange_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT) }
Vk.put_i32(vci, sr + VkImageSubresourceRange_levelCount, 1)
Vk.put_i32(vci, sr + VkImageSubresourceRange_baseArrayLayer, layer1 - 1)
if layer1 > 0 { Vk.put_i32(vci, sr + VkImageSubresourceRange_baseArrayLayer, layer1 - 1) }
Vk.put_i32(vci, sr + VkImageSubresourceRange_layerCount, 1)
let out = bytes(8)
let zero: long = 0