renderer+qt/vulkan: bg_color pipeline draws + dynamicRendering on host
First pipeline actually drawing. Validation passes (modulo NVIDIA's
unrelated `VK_KHR_present_mode_fifo_latest_ready` noise). The window
should now show the rendered output of bg_color rather than a
transparent rectangle — verify visually.
What changed:
Pipeline / Shaders:
- `Pipeline.zig` gains optional `descriptor_pool` in `Options`,
plus `descriptor_set` + `descriptor_set_layout` fields. When
given a pool + layouts, `Pipeline.init` allocates one set so
`RenderPass.step` can bind it without separate plumbing.
- `Shaders.init` builds the real bg_color pipeline:
`full_screen.v.glsl` + `bg_color.f.glsl`, UBO at binding=1
(matching the GLSL `layout(binding = 1)` on Globals), fragment
stage only. Pool sized for 5 sets + 5 UBOs + 8 image-samplers
so adding the remaining pipelines later doesn't need a pool
rebuild.
- `PipelineCollection`'s default-init now uses a zeroed
`empty_pipeline` sentinel (`pipeline == null`) instead of
`undefined`. Debug-mode 0xAA poison was reaching validation as
fake VkPipeline / VkDescriptorSet handles.
RenderPass.step body:
- Skips silently when `pipeline.pipeline == null` (the four
unbuilt slots in PipelineCollection).
- For pipelines that do have a descriptor set, updates it with
the Step's `uniforms` VkBuffer (UBO descriptor type), binds
the set, then binds the pipeline and emits `vkCmdDraw`.
Target.zig:
- Adds COLOR_ATTACHMENT + TRANSFER_SRC to the usage flags so
the target is valid as both a render-pass attachment and a
debug-readback source. SAMPLED was already there for the
custom-shader path.
Vulkan.textureOptions:
- Bumps to `B8G8R8A8_UNORM` (matching `initTarget`) and adds
COLOR_ATTACHMENT_BIT. The renderer's custom-shader
`back_texture` is BOTH a render target AND a sampled source,
so the usage union covers both roles. Without this, the
custom-shader path (which the user's config triggers) tried
to use a SAMPLED-only image as a color attachment and validation
rejected it.
shaders.zig:
- For v1, only compile `full_screen.v.glsl` + `bg_color.f.glsl`.
The other 7 shaders use `sampler2DRect`, which is an OpenGL-only
construct that produces SPIR-V with the `SampledRect` capability
Vulkan 1.3 doesn't allow. Source-level rewrite to `sampler2D`
is a separate follow-up. Unused module slots stay null-handle
sentinels; `deinit` skips them.
Host (Qt side):
- Enables `VkPhysicalDeviceVulkan13Features.dynamicRendering` +
`synchronization2` when creating the VkDevice. libghostty's
renderer uses Vulkan 1.3 dynamic rendering
(`vkCmdBeginRendering` / `vkCmdEndRendering`, no
`VkRenderPass`); the feature must be explicitly enabled at
device creation or the renderer errors when it tries to begin
a rendering scope.
Co-Authored-By: claude-flow <ruv@ruv.net>
pull/12846/head
parent
f1c4fa60b9
commit
e8ad547dda
|
|
@ -176,8 +176,18 @@ bool Host::init() {
|
|||
qci.queueCount = 1;
|
||||
qci.pQueuePriorities = &queuePriority;
|
||||
|
||||
// libghostty's Vulkan renderer uses Vulkan 1.3 dynamic rendering
|
||||
// (vkCmdBeginRendering / vkCmdEndRendering, no VkRenderPass).
|
||||
// That feature has to be explicitly enabled at device creation
|
||||
// time via VkPhysicalDeviceVulkan13Features.
|
||||
VkPhysicalDeviceVulkan13Features vk13features{};
|
||||
vk13features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
|
||||
vk13features.dynamicRendering = VK_TRUE;
|
||||
vk13features.synchronization2 = VK_TRUE;
|
||||
|
||||
VkDeviceCreateInfo dci{};
|
||||
dci.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||
dci.pNext = &vk13features;
|
||||
dci.queueCreateInfoCount = 1;
|
||||
dci.pQueueCreateInfos = &qci;
|
||||
dci.enabledExtensionCount =
|
||||
|
|
|
|||
|
|
@ -385,11 +385,19 @@ pub fn bgImageBufferOptions(self: *const Vulkan) bufferpkg.Options {
|
|||
}
|
||||
|
||||
pub fn textureOptions(_: *const Vulkan) Texture.Options {
|
||||
// The renderer uses `textureOptions()`-shaped textures both for
|
||||
// glyph atlases (sampled-only) AND for the custom-shader
|
||||
// back_texture (which is BOTH sampled AND a render target).
|
||||
// We hand back the wider usage set so both work. The format
|
||||
// matches the renderer's `initTarget` choice
|
||||
// (`B8G8R8A8_UNORM`) so a render → sample → render chain
|
||||
// through the custom-shader pass keeps the same color format.
|
||||
return .{
|
||||
.device = devicePtr(),
|
||||
.format = vk.VK_FORMAT_R8G8B8A8_UNORM,
|
||||
.format = vk.VK_FORMAT_B8G8R8A8_UNORM,
|
||||
.usage = vk.VK_IMAGE_USAGE_SAMPLED_BIT |
|
||||
vk.VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
||||
vk.VK_IMAGE_USAGE_TRANSFER_DST_BIT |
|
||||
vk.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const std = @import("std");
|
|||
const vk = @import("vulkan").c;
|
||||
|
||||
const Device = @import("Device.zig");
|
||||
const DescriptorPool = @import("DescriptorPool.zig");
|
||||
|
||||
const log = std.log.scoped(.vulkan);
|
||||
|
||||
|
|
@ -56,6 +57,13 @@ pub const VertexInput = struct {
|
|||
pub const Options = struct {
|
||||
device: *const Device,
|
||||
|
||||
/// Optional descriptor pool. If provided alongside a non-empty
|
||||
/// `descriptor_set_layouts` slice, `Pipeline.init` allocates one
|
||||
/// descriptor set against the first layout and stores it on
|
||||
/// `Pipeline.descriptor_set` so `RenderPass.step` can bind it
|
||||
/// without a separate plumbing step.
|
||||
descriptor_pool: ?*DescriptorPool = null,
|
||||
|
||||
/// Shader modules. The caller owns these — Pipeline does not
|
||||
/// destroy them on deinit (they're typically reused across
|
||||
/// multiple pipelines and outlive any one of them).
|
||||
|
|
@ -95,6 +103,25 @@ device: *const Device,
|
|||
pipeline: vk.VkPipeline,
|
||||
layout: vk.VkPipelineLayout,
|
||||
|
||||
/// Cached copy of the single `VkDescriptorSetLayout` this pipeline
|
||||
/// was built with (when one was provided). `Shaders.init` owns the
|
||||
/// layout's lifetime; storing the handle here lets `RenderPass.step`
|
||||
/// allocate descriptor sets matching this pipeline without threading
|
||||
/// the layout separately.
|
||||
descriptor_set_layout: vk.VkDescriptorSetLayout = null,
|
||||
|
||||
/// Optional descriptor set bundled with this pipeline. When set,
|
||||
/// `RenderPass.step` updates it with the Step's `uniforms`/textures
|
||||
/// and binds it before drawing. Allocated from a pool at
|
||||
/// `Pipeline.init` time when `opts.descriptor_pool` is provided.
|
||||
/// Null for pipelines that take no descriptor inputs (e.g. the
|
||||
/// smoke-test's solid-color pipeline).
|
||||
descriptor_set: vk.VkDescriptorSet = null,
|
||||
/// Binding number that `uniforms` writes to. Defaults to 1 to match
|
||||
/// the GLSL `layout(binding = 1)` on the Globals UBO. Override per
|
||||
/// pipeline if/when glslang's auto-map picks a different slot.
|
||||
uniforms_binding: u32 = 1,
|
||||
|
||||
pub fn init(opts: Options) Error!Self {
|
||||
const dev = opts.device;
|
||||
|
||||
|
|
@ -312,10 +339,25 @@ pub fn init(opts: Options) Error!Self {
|
|||
}
|
||||
}
|
||||
|
||||
const dsl_first: vk.VkDescriptorSetLayout =
|
||||
if (opts.descriptor_set_layouts.len > 0) opts.descriptor_set_layouts[0] else null;
|
||||
|
||||
var dset: vk.VkDescriptorSet = null;
|
||||
if (opts.descriptor_pool) |pool_ptr| {
|
||||
if (dsl_first != null) {
|
||||
dset = pool_ptr.allocate(dsl_first) catch |err| {
|
||||
log.err("Pipeline.init: descriptor set allocation failed: {}", .{err});
|
||||
return error.VulkanFailed;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return .{
|
||||
.device = dev,
|
||||
.pipeline = pipeline,
|
||||
.layout = layout,
|
||||
.descriptor_set_layout = dsl_first,
|
||||
.descriptor_set = dset,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -213,19 +213,70 @@ pub fn begin(opts: Options) Self {
|
|||
|
||||
/// Record one step of the pass.
|
||||
///
|
||||
/// **Body is a stub.** The full implementation will bind the
|
||||
/// pipeline, allocate + populate the descriptor set, bind vertex
|
||||
/// buffers, and emit `vkCmdDraw`. Until that lands, step records
|
||||
/// nothing — the frame loop runs end-to-end without drawing real
|
||||
/// terminal content but doesn't crash either, so the rest of the
|
||||
/// Vulkan integration (Qt-side QRhiWidget + dmabuf import) can
|
||||
/// proceed in parallel against a known-color clear frame.
|
||||
/// Skips silently when the pipeline isn't yet real (`VkPipeline ==
|
||||
/// null`) — `Shaders.init` only constructs bg_color so far; the
|
||||
/// other 4 pipeline slots are default-undefined and we filter them
|
||||
/// out here rather than crashing on a null handle.
|
||||
pub fn step(self: *Self, s: Step) void {
|
||||
_ = self;
|
||||
_ = s;
|
||||
// No-op stub. Replace with `cmdBindPipeline` + descriptor set
|
||||
// wiring + `cmdDraw` once Shaders.init + DescriptorPool
|
||||
// integration lands.
|
||||
// Skip pipelines that haven't been constructed yet — only
|
||||
// `bg_color` is real today; the other 4 slots in
|
||||
// `PipelineCollection` are default-initialized (VkPipeline ==
|
||||
// null) and we filter them out instead of crashing on a null
|
||||
// handle.
|
||||
if (s.pipeline.pipeline == null) return;
|
||||
if (s.draw.vertex_count == 0) return;
|
||||
|
||||
const dev = self.device;
|
||||
|
||||
// Update + bind the pipeline's descriptor set if it has one
|
||||
// AND the step is passing a uniforms buffer. Today this only
|
||||
// fires for the bg_color path.
|
||||
if (s.pipeline.descriptor_set != null) if (s.uniforms) |ubo_buffer| {
|
||||
const buffer_info: vk.VkDescriptorBufferInfo = .{
|
||||
.buffer = ubo_buffer,
|
||||
.offset = 0,
|
||||
.range = vk.VK_WHOLE_SIZE,
|
||||
};
|
||||
const write: vk.VkWriteDescriptorSet = .{
|
||||
.sType = vk.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.pNext = null,
|
||||
.dstSet = s.pipeline.descriptor_set,
|
||||
.dstBinding = s.pipeline.uniforms_binding,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
.pImageInfo = null,
|
||||
.pBufferInfo = &buffer_info,
|
||||
.pTexelBufferView = null,
|
||||
};
|
||||
dev.dispatch.updateDescriptorSets(dev.device, 1, &write, 0, null);
|
||||
|
||||
var sets = [_]vk.VkDescriptorSet{s.pipeline.descriptor_set};
|
||||
dev.dispatch.cmdBindDescriptorSets(
|
||||
self.cb,
|
||||
vk.VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
s.pipeline.layout,
|
||||
0, // first set
|
||||
1, // set count
|
||||
&sets,
|
||||
0, // dynamic offset count
|
||||
null,
|
||||
);
|
||||
};
|
||||
|
||||
dev.dispatch.cmdBindPipeline(
|
||||
self.cb,
|
||||
vk.VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
s.pipeline.pipeline,
|
||||
);
|
||||
dev.dispatch.cmdDraw(
|
||||
self.cb,
|
||||
@intCast(s.draw.vertex_count),
|
||||
@intCast(s.draw.instance_count),
|
||||
0,
|
||||
0,
|
||||
);
|
||||
self.step_number += 1;
|
||||
}
|
||||
|
||||
/// Close the rendering scope and leave the attachment in a layout
|
||||
|
|
|
|||
|
|
@ -106,8 +106,12 @@ pub fn init(opts: Options) Error!Self {
|
|||
const dev = opts.device;
|
||||
const drm_format = try vkFormatToDrmFourcc(opts.format);
|
||||
|
||||
// COLOR_ATTACHMENT — we render into this via dynamic rendering.
|
||||
// SAMPLED — the renderer's custom-shader path samples the target.
|
||||
// TRANSFER_SRC — readback for debug / screenshot tooling.
|
||||
const usage = @as(vk.VkImageUsageFlags, vk.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) |
|
||||
vk.VK_IMAGE_USAGE_SAMPLED_BIT |
|
||||
vk.VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
|
||||
opts.extra_usage;
|
||||
|
||||
// ---- 1. VkImage (with external-memory chain) ----------------
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const glslang = @import("glslang");
|
|||
|
||||
const Device = @import("Device.zig");
|
||||
const Pipeline = @import("Pipeline.zig");
|
||||
const DescriptorPool = @import("DescriptorPool.zig");
|
||||
const math = @import("../../math.zig");
|
||||
|
||||
const log = std.log.scoped(.vulkan);
|
||||
|
|
@ -377,12 +378,24 @@ pub const BgImage = extern struct {
|
|||
|
||||
/// Pipeline collection shape (matches `opengl/shaders.zig`). Each
|
||||
/// field is the Vulkan `Pipeline` instance for that named shader.
|
||||
///
|
||||
/// Default-init to all-null handles: pipelines that haven't been
|
||||
/// constructed yet have `pipeline == null`, which `RenderPass.step`
|
||||
/// detects and silently skips. Using `Pipeline = undefined` instead
|
||||
/// would leak Debug-mode 0xAA poison bytes into VkPipeline / VkDevice
|
||||
/// handles, which validation rightly flags as invalid.
|
||||
pub const PipelineCollection = struct {
|
||||
bg_color: Pipeline = undefined,
|
||||
cell_bg: Pipeline = undefined,
|
||||
cell_text: Pipeline = undefined,
|
||||
image: Pipeline = undefined,
|
||||
bg_image: Pipeline = undefined,
|
||||
bg_color: Pipeline = empty_pipeline,
|
||||
cell_bg: Pipeline = empty_pipeline,
|
||||
cell_text: Pipeline = empty_pipeline,
|
||||
image: Pipeline = empty_pipeline,
|
||||
bg_image: Pipeline = empty_pipeline,
|
||||
};
|
||||
|
||||
const empty_pipeline: Pipeline = .{
|
||||
.device = undefined, // unused — gated behind pipeline-handle null checks
|
||||
.pipeline = null,
|
||||
.layout = null,
|
||||
};
|
||||
|
||||
/// Top-level renderer shader state. Same shape as
|
||||
|
|
@ -406,6 +419,21 @@ pub const Shaders = struct {
|
|||
pipelines: PipelineCollection,
|
||||
post_pipelines: []const Pipeline,
|
||||
modules: Modules,
|
||||
|
||||
/// Process-wide descriptor pool. Sized for one set per pipeline
|
||||
/// at startup; `RenderPass.step` updates the sets in place each
|
||||
/// frame (we wait on the fence in `Frame.complete`, so reuse is
|
||||
/// safe — no command buffer using these sets is in flight when
|
||||
/// the next frame begins).
|
||||
descriptor_pool: ?DescriptorPool = null,
|
||||
|
||||
/// One descriptor set + layout per pipeline. The layout is also
|
||||
/// stored on `Pipeline.descriptor_set_layout` so `RenderPass.step`
|
||||
/// can re-fetch from `step.pipeline`; the set lives here because
|
||||
/// it's allocated once and updated per-frame.
|
||||
bg_color_set_layout: vk.VkDescriptorSetLayout = null,
|
||||
bg_color_set: vk.VkDescriptorSet = null,
|
||||
|
||||
defunct: bool = false,
|
||||
|
||||
/// The compiled `VkShaderModule`s for the renderer's built-in
|
||||
|
|
@ -436,29 +464,108 @@ pub const Shaders = struct {
|
|||
// tears down any successfully-compiled modules if a later
|
||||
// one fails so we don't leak `VkShaderModule` handles on
|
||||
// partial failure.
|
||||
var modules: Modules = undefined;
|
||||
modules.bg_color_frag = try Module.init(alloc, device, source.bg_color_frag, .fragment);
|
||||
errdefer modules.bg_color_frag.deinit();
|
||||
modules.bg_image_frag = try Module.init(alloc, device, source.bg_image_frag, .fragment);
|
||||
errdefer modules.bg_image_frag.deinit();
|
||||
modules.bg_image_vert = try Module.init(alloc, device, source.bg_image_vert, .vertex);
|
||||
errdefer modules.bg_image_vert.deinit();
|
||||
modules.cell_bg_frag = try Module.init(alloc, device, source.cell_bg_frag, .fragment);
|
||||
errdefer modules.cell_bg_frag.deinit();
|
||||
modules.cell_text_frag = try Module.init(alloc, device, source.cell_text_frag, .fragment);
|
||||
errdefer modules.cell_text_frag.deinit();
|
||||
modules.cell_text_vert = try Module.init(alloc, device, source.cell_text_vert, .vertex);
|
||||
errdefer modules.cell_text_vert.deinit();
|
||||
// For v1 we only compile the modules needed by the bg_color
|
||||
// pipeline (`full_screen.v.glsl` + `bg_color.f.glsl`). The
|
||||
// other shaders use OpenGL-only constructs (`sampler2DRect`)
|
||||
// that aren't valid SPIR-V capabilities in Vulkan 1.3 — they
|
||||
// need source-level conversion to `sampler2D` before we can
|
||||
// compile them. The unused modules stay null-handle
|
||||
// sentinels and `Shaders.deinit` skips them.
|
||||
const empty_module: Module = .{
|
||||
.handle = null,
|
||||
.stage = vk.VK_SHADER_STAGE_VERTEX_BIT,
|
||||
.device = device,
|
||||
};
|
||||
var modules: Modules = .{
|
||||
.bg_color_frag = empty_module,
|
||||
.bg_image_frag = empty_module,
|
||||
.bg_image_vert = empty_module,
|
||||
.cell_bg_frag = empty_module,
|
||||
.cell_text_frag = empty_module,
|
||||
.cell_text_vert = empty_module,
|
||||
.full_screen_vert = empty_module,
|
||||
.image_frag = empty_module,
|
||||
.image_vert = empty_module,
|
||||
};
|
||||
modules.full_screen_vert = try Module.init(alloc, device, source.full_screen_vert, .vertex);
|
||||
errdefer modules.full_screen_vert.deinit();
|
||||
modules.image_frag = try Module.init(alloc, device, source.image_frag, .fragment);
|
||||
errdefer modules.image_frag.deinit();
|
||||
modules.image_vert = try Module.init(alloc, device, source.image_vert, .vertex);
|
||||
modules.bg_color_frag = try Module.init(alloc, device, source.bg_color_frag, .fragment);
|
||||
errdefer modules.bg_color_frag.deinit();
|
||||
|
||||
// Build a descriptor pool sized for one descriptor set per
|
||||
// pipeline (we currently only construct bg_color; size for the
|
||||
// full set so adding new pipelines doesn't require pool
|
||||
// resizing).
|
||||
var pool = try DescriptorPool.init(.{
|
||||
.device = device,
|
||||
.max_sets = 5,
|
||||
.uniform_buffers = 5,
|
||||
.combined_image_samplers = 8,
|
||||
});
|
||||
errdefer pool.deinit();
|
||||
|
||||
// ---- bg_color pipeline -----------------------------------
|
||||
//
|
||||
// Full-screen fragment shader that reads the bg color out of
|
||||
// the Globals UBO. The vertex shader (`full_screen.v.glsl`)
|
||||
// synthesizes a covering triangle from `gl_VertexIndex`, so
|
||||
// there's no vertex input.
|
||||
//
|
||||
// Descriptor set layout: one UBO binding for Globals. The
|
||||
// existing OpenGL shader declares it at `binding = 1`; with
|
||||
// glslang's `setAutoMapBindings(true)` (in our shim) the
|
||||
// binding may be remapped, but for v1 we declare it at
|
||||
// binding 1 to match. Layout fragment-stage only — the
|
||||
// vertex shader for bg_color doesn't use the UBO.
|
||||
const bg_color_bindings = [_]vk.VkDescriptorSetLayoutBinding{.{
|
||||
.binding = 1,
|
||||
.descriptorType = vk.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = vk.VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
.pImmutableSamplers = null,
|
||||
}};
|
||||
const bg_color_dsl_info: vk.VkDescriptorSetLayoutCreateInfo = .{
|
||||
.sType = vk.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.pNext = null,
|
||||
.flags = 0,
|
||||
.bindingCount = bg_color_bindings.len,
|
||||
.pBindings = &bg_color_bindings,
|
||||
};
|
||||
var bg_color_dsl: vk.VkDescriptorSetLayout = undefined;
|
||||
if (device.dispatch.createDescriptorSetLayout(
|
||||
device.device,
|
||||
&bg_color_dsl_info,
|
||||
null,
|
||||
&bg_color_dsl,
|
||||
) != vk.VK_SUCCESS) {
|
||||
return error.VulkanFailed;
|
||||
}
|
||||
errdefer device.dispatch.destroyDescriptorSetLayout(device.device, bg_color_dsl, null);
|
||||
|
||||
const bg_color_dsls = [_]vk.VkDescriptorSetLayout{bg_color_dsl};
|
||||
const bg_color_pipeline = try Pipeline.init(.{
|
||||
.device = device,
|
||||
.descriptor_pool = &pool,
|
||||
.vertex_module = modules.full_screen_vert.handle,
|
||||
.fragment_module = modules.bg_color_frag.handle,
|
||||
.vertex_input = null,
|
||||
.descriptor_set_layouts = &bg_color_dsls,
|
||||
.color_format = vk.VK_FORMAT_B8G8R8A8_UNORM,
|
||||
.blending_enabled = false,
|
||||
.topology = vk.VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
|
||||
});
|
||||
errdefer bg_color_pipeline.deinit();
|
||||
|
||||
var pipelines: PipelineCollection = .{};
|
||||
pipelines.bg_color = bg_color_pipeline;
|
||||
|
||||
return .{
|
||||
.pipelines = .{},
|
||||
.pipelines = pipelines,
|
||||
.post_pipelines = &.{},
|
||||
.modules = modules,
|
||||
.descriptor_pool = pool,
|
||||
.bg_color_set_layout = bg_color_dsl,
|
||||
.bg_color_set = bg_color_pipeline.descriptor_set,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -467,20 +574,40 @@ pub const Shaders = struct {
|
|||
if (self.defunct) return;
|
||||
self.defunct = true;
|
||||
|
||||
// Destroy every compiled module.
|
||||
self.modules.bg_color_frag.deinit();
|
||||
self.modules.bg_image_frag.deinit();
|
||||
self.modules.bg_image_vert.deinit();
|
||||
self.modules.cell_bg_frag.deinit();
|
||||
self.modules.cell_text_frag.deinit();
|
||||
self.modules.cell_text_vert.deinit();
|
||||
self.modules.full_screen_vert.deinit();
|
||||
self.modules.image_frag.deinit();
|
||||
self.modules.image_vert.deinit();
|
||||
// Real pipeline (bg_color) — destroy first since it
|
||||
// references the descriptor set layout.
|
||||
const bg_color_real = self.pipelines.bg_color.pipeline != null;
|
||||
if (bg_color_real) self.pipelines.bg_color.deinit();
|
||||
|
||||
// No pipeline destruction yet — `init` doesn't construct
|
||||
// real pipelines. Real `deinit` will iterate `inline for`
|
||||
// over PipelineCollection's fields once those exist.
|
||||
// The descriptor pool reclaims all sets allocated from it,
|
||||
// including `bg_color_set`. Destroy the standalone layout
|
||||
// separately.
|
||||
if (self.descriptor_pool) |*p| p.deinit();
|
||||
if (self.bg_color_set_layout != null) {
|
||||
self.modules.bg_color_frag.device.dispatch.destroyDescriptorSetLayout(
|
||||
self.modules.bg_color_frag.device.device,
|
||||
self.bg_color_set_layout,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
// Destroy every compiled module. Modules whose handle is
|
||||
// null (not compiled in v1) skip destruction — vkDestroy*
|
||||
// is null-safe per the Vulkan spec but we check explicitly
|
||||
// so we don't even pass null through the dispatch.
|
||||
inline for (.{
|
||||
&self.modules.bg_color_frag,
|
||||
&self.modules.bg_image_frag,
|
||||
&self.modules.bg_image_vert,
|
||||
&self.modules.cell_bg_frag,
|
||||
&self.modules.cell_text_frag,
|
||||
&self.modules.cell_text_vert,
|
||||
&self.modules.full_screen_vert,
|
||||
&self.modules.image_frag,
|
||||
&self.modules.image_vert,
|
||||
}) |m_ptr| {
|
||||
if (m_ptr.handle != null) m_ptr.deinit();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue