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>
gpu.ludic's calls branch to the Vulkan backend when it was chosen and came up; every OpenGL
statement is unchanged, only guarded. R3D_GFX=vk selects it in a headless run (the window's
swapchain is the next milestone) and falls back to OpenGL, with the reason, when the device or
the SPIR-V manifest is missing.
- Render state is cached as before and turned into pipelines at the draw; u_* and sampler binds
go into the variant's uniform blocks; meshes, buffers and textures are gvk_* objects;
framebuffer binds are dynamic-rendering passes; same-size blits are image copies; the screen,
the photograph read-back, the present and the screenshot go through the frame.
- Work that submits on its own (uploads, read-backs, new or freed images and buffers) flushes the
frame first, so it runs in OpenGL's order. A read may take fewer channels than the image has
(the height field's R from its RGBA32F bake). Pipeline keys name vertex bindings by order, not
buffer handle, so re-pointed instance buffers keep their pipeline.
The valley renders at frame 90 validation-clean on the RTX 3070 Ti and on MoltenVK. OpenGL frames
byte-identical at the five viewpoints; 59 self-tests pass with no GL error.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The drawing half of the Vulkan backend, compiled into render3d and not yet reached from it:
- gvk_program: a program handle as its manifest variant - two SPIR-V modules, a descriptor set
layout (the vertex block at 0, the fragment block at 1, the samplers at their manifest bindings)
and a pipeline layout. Nothing is compiled at run time.
- gvk_pipeline: one pipeline per program, recorded vertex layout, render state and pass formats,
built the first time that combination draws. Attributes the shader does not read are left out;
the front face is clockwise, since neither API flips y between clip space and its target rows.
- gvk_uniform / gvk_u_set / gvk_bind_texture: loose uniforms written into each stage's block at
the manifest's offsets and array strides; gvk_draw_set copies the blocks into a per-frame ring
at the device's alignment and fills a descriptor set from a per-frame pool, with a white 1x1
texture for a sampler nothing was bound to.
- The frame: one command buffer; a framebuffer bind ends the pass and the next begins at its first
clear or draw (a clear that comes first is the load op); attachments move to attachment layouts
for the pass and back to SHADER_READ_ONLY after it, a cascade drawn through a view of its layer.
gvk_present submits and waits; gvk_screenshot reads the screen image back bottom row first.
r3d.ludic now imports gpu_manifest.ludic too. Textures remember their size.
OpenGL frames byte-identical at the five viewpoints; 59 self-tests pass; VKRES OK and VKDEVICE OK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gpu_vk_res.ludic gains what gpu.ludic's texture and buffer handles stand for on Vulkan:
- gvk_tex_storage / _upload / _mips / _read / _release: an image and view per handle, a full mip
chain when pixels come with it (the renderer asks for mipmaps after the upload) and one level
for a target, every level in SHADER_READ_ONLY between uses. Uploads are converted to what the
image stores: a missing alpha filled opaque (three-channel formats are stored with four), 16-bit
PNG samples byte-swapped when the unpack state says so, 32-bit float HDR halved into half
floats. Mips are blitted down level by level; a read-back brings level 0 home.
- gvk_sampler: one VkSampler per filter / wrap / compare / anisotropy combination, made when first
asked for, with GL's defaults where the renderer set nothing.
- gvk_buf_*: vertex, index and instance buffers behind one handle, kept when an upload fits.
Host-visible while the backend comes up.
gpu_vk.ludic switches on anisotropic sampling where the device has it and reads its limit.
examples/rendering/vk_resources.ludic checks it all: VKRES OK, validation-clean, every allocation
freed, on the RTX 3070 Ti (16x anisotropy) and on MoltenVK. One run on the Mac crashed while a
headless game run was using the GPU and did not come back in two reruns. OpenGL frames
byte-identical at the five viewpoints; 59 self-tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Vulkan backend is two files. gpu_vk.ludic is the device, memory and one-shot commands,
pure Vulkan, and still runs on its own (vk_device.ludic). gpu_vk_res.ludic is the resources in
the renderer's vocabulary - OpenGL's names for formats, filters and blend factors, which only
exist where Gl.* is named - starting with the format table. r3d.ludic imports both after
gpu.ludic; nothing calls them yet.
OpenGL frames byte-identical at the five viewpoints; 59 self-tests pass; VKDEVICE OK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>