2026-07-22 18:09:06 +02:00
|
|
|
//SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
2026-01-27 22:34:24 +01:00
|
|
|
|
|
|
|
|
module;
|
2026-05-18 02:07:48 +02:00
|
|
|
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
2026-03-02 23:53:13 +01:00
|
|
|
#include "vulkan/vulkan.h"
|
2026-01-27 22:34:24 +01:00
|
|
|
#include <assert.h>
|
2026-05-18 02:07:48 +02:00
|
|
|
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
|
2026-01-27 22:34:24 +01:00
|
|
|
export module Crafter.Graphics:VulkanTransition;
|
2026-05-18 02:07:48 +02:00
|
|
|
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
2026-01-27 22:34:24 +01:00
|
|
|
import std;
|
feat(vulkan): run the compute/UI path without VK_EXT_descriptor_heap
Device::Initialize required twelve device extensions with no capability
check. Mesa Turnip (Adreno) lacks six of them: VK_EXT_descriptor_heap,
VK_KHR_shader_untyped_pointers, VK_EXT_device_fault and the three ray
tracing extensions. vkCreateDevice failed there, so not even a UI-only
program could start, although the UI path is compute-only and needs
nothing else that Turnip is missing.
The descriptor heap, ray tracing and device fault are now optional
groups, detected at start-up from the extension list and the feature
bits (Device::descriptorHeapSupported, rayTracingSupported,
deviceFaultSupported). Ray tracing needs the heap; on a device without
it the ray tracing entry points throw a message naming the cause.
Without the heap the same bindless slots live in one update-after-bind
descriptor set per frame, owned by DescriptorHeapVulkan: binding 0
storage images, 1 sampled images, 2 samplers, 3 storage buffers. Slot
allocation, the slot handles, UIDispatchHeader and the UIRenderer API
are unchanged; buffer slots simply stop being offset. Window::Render
binds that frame's set where it binds the heap, every compute pipeline
shares the heap object's pipeline layout, and push data travels as push
constants.
Each UI kernel is built twice from one source. ui-shared.glsl declares
its resources through UI_HEAP_* macros that expand to descriptor_heap
or, with CRAFTER_GRAPHICS_DESCRIPTOR_SETS defined, to set/binding.
<name>.comp.spv and <name>.comp.sets.spv ship side by side and
ComputeShader::Load picks one. The heap build of every shader is
byte-identical to before.
Breaking:
- ComputeShader::Load takes the heap as a second argument.
- DescriptorHeapVulkan is no longer copyable or movable.
- Custom UI shaders declare heap resources with the UI_HEAP_* macros
and are registered twice in project.cpp (see the README).
- project.cpp uses Shader::defines and Shader::variant, so it needs a
crafter-build that has them (Crafter.Build master, fe38cc1 or later).
Also:
- maintenance5 is enabled as a feature, not only as an extension:
every VulkanBuffer already relied on VkBufferUsageFlags2CreateInfo.
- Barrier stage masks drop RAY_TRACING_SHADER on devices without ray
tracing, and ImageVulkan::Create resolves its default consumer stage
to COMPUTE_SHADER there.
- Window refuses a swapchain whose image count is not numFrames
instead of writing past images[]. The limitation itself is #160.
- CRAFTER_GRAPHICS_NO_DESCRIPTOR_HEAP forces the fallback on a driver
that has the heap, so it can be exercised on a desktop GPU.
Verified on an RTX 4090 in both modes under the validation layer, and
on a Fairphone 6 (Turnip, Adreno 810, postmarketOS), where the fallback
is the only path.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 17:28:46 +02:00
|
|
|
import :Device;
|
2026-01-27 22:34:24 +01:00
|
|
|
|
|
|
|
|
export namespace Crafter {
|
2026-03-02 23:53:13 +01:00
|
|
|
#include "vulkan/vulkan.h"
|
2026-01-27 22:34:24 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
VkAccessFlags getAccessFlags(VkImageLayout layout)
|
|
|
|
|
{
|
|
|
|
|
switch (layout)
|
|
|
|
|
{
|
|
|
|
|
case VK_IMAGE_LAYOUT_UNDEFINED:
|
|
|
|
|
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
|
|
|
|
|
return 0;
|
|
|
|
|
case VK_IMAGE_LAYOUT_PREINITIALIZED:
|
|
|
|
|
return VK_ACCESS_HOST_WRITE_BIT;
|
|
|
|
|
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
|
|
|
|
|
return VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
|
|
|
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL:
|
|
|
|
|
return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
|
|
|
|
case VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR:
|
|
|
|
|
return VK_ACCESS_FRAGMENT_SHADING_RATE_ATTACHMENT_READ_BIT_KHR;
|
|
|
|
|
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
|
|
|
|
|
return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
|
|
|
|
|
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
|
|
|
|
|
return VK_ACCESS_TRANSFER_READ_BIT;
|
|
|
|
|
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
|
|
|
|
|
return VK_ACCESS_TRANSFER_WRITE_BIT;
|
|
|
|
|
case VK_IMAGE_LAYOUT_GENERAL:
|
2026-01-29 02:05:18 +01:00
|
|
|
//assert(false && "Don't know how to get a meaningful VkAccessFlags for VK_IMAGE_LAYOUT_GENERAL! Don't use it!");
|
|
|
|
|
return VK_ACCESS_SHADER_WRITE_BIT;
|
2026-01-27 22:34:24 +01:00
|
|
|
default:
|
|
|
|
|
assert(false);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VkPipelineStageFlags getPipelineStageFlags(VkImageLayout layout)
|
|
|
|
|
{
|
|
|
|
|
switch (layout)
|
|
|
|
|
{
|
|
|
|
|
case VK_IMAGE_LAYOUT_UNDEFINED:
|
|
|
|
|
return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
|
|
|
|
case VK_IMAGE_LAYOUT_PREINITIALIZED:
|
|
|
|
|
return VK_PIPELINE_STAGE_HOST_BIT;
|
|
|
|
|
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
|
|
|
|
|
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
|
|
|
|
|
return VK_PIPELINE_STAGE_TRANSFER_BIT;
|
|
|
|
|
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
|
|
|
|
|
return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
|
|
|
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL:
|
|
|
|
|
return VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
|
|
|
|
case VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR:
|
|
|
|
|
return VK_PIPELINE_STAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR;
|
|
|
|
|
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
|
|
|
|
|
return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
|
|
|
|
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
|
|
|
|
|
return VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
|
|
|
|
case VK_IMAGE_LAYOUT_GENERAL:
|
2026-01-29 02:05:18 +01:00
|
|
|
//assert(false && "Don't know how to get a meaningful VkPipelineStageFlags for VK_IMAGE_LAYOUT_GENERAL! Don't use it!");
|
feat(vulkan): run the compute/UI path without VK_EXT_descriptor_heap
Device::Initialize required twelve device extensions with no capability
check. Mesa Turnip (Adreno) lacks six of them: VK_EXT_descriptor_heap,
VK_KHR_shader_untyped_pointers, VK_EXT_device_fault and the three ray
tracing extensions. vkCreateDevice failed there, so not even a UI-only
program could start, although the UI path is compute-only and needs
nothing else that Turnip is missing.
The descriptor heap, ray tracing and device fault are now optional
groups, detected at start-up from the extension list and the feature
bits (Device::descriptorHeapSupported, rayTracingSupported,
deviceFaultSupported). Ray tracing needs the heap; on a device without
it the ray tracing entry points throw a message naming the cause.
Without the heap the same bindless slots live in one update-after-bind
descriptor set per frame, owned by DescriptorHeapVulkan: binding 0
storage images, 1 sampled images, 2 samplers, 3 storage buffers. Slot
allocation, the slot handles, UIDispatchHeader and the UIRenderer API
are unchanged; buffer slots simply stop being offset. Window::Render
binds that frame's set where it binds the heap, every compute pipeline
shares the heap object's pipeline layout, and push data travels as push
constants.
Each UI kernel is built twice from one source. ui-shared.glsl declares
its resources through UI_HEAP_* macros that expand to descriptor_heap
or, with CRAFTER_GRAPHICS_DESCRIPTOR_SETS defined, to set/binding.
<name>.comp.spv and <name>.comp.sets.spv ship side by side and
ComputeShader::Load picks one. The heap build of every shader is
byte-identical to before.
Breaking:
- ComputeShader::Load takes the heap as a second argument.
- DescriptorHeapVulkan is no longer copyable or movable.
- Custom UI shaders declare heap resources with the UI_HEAP_* macros
and are registered twice in project.cpp (see the README).
- project.cpp uses Shader::defines and Shader::variant, so it needs a
crafter-build that has them (Crafter.Build master, fe38cc1 or later).
Also:
- maintenance5 is enabled as a feature, not only as an extension:
every VulkanBuffer already relied on VkBufferUsageFlags2CreateInfo.
- Barrier stage masks drop RAY_TRACING_SHADER on devices without ray
tracing, and ImageVulkan::Create resolves its default consumer stage
to COMPUTE_SHADER there.
- Window refuses a swapchain whose image count is not numFrames
instead of writing past images[]. The limitation itself is #160.
- CRAFTER_GRAPHICS_NO_DESCRIPTOR_HEAP forces the fallback on a driver
that has the heap, so it can be exercised on a desktop GPU.
Verified on an RTX 4090 in both modes under the validation layer, and
on a Fairphone 6 (Turnip, Adreno 810, postmarketOS), where the fallback
is the only path.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 17:28:46 +02:00
|
|
|
// GENERAL images are written by RT or compute shaders; on a
|
|
|
|
|
// device without ray tracing the RT stage is not a valid stage.
|
|
|
|
|
return Device::rayTracingSupported ? VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR
|
|
|
|
|
: VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
|
2026-01-27 22:34:24 +01:00
|
|
|
default:
|
|
|
|
|
assert(false);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create an image memory barrier for changing the layout of
|
|
|
|
|
// an image and put it into an active command buffer
|
|
|
|
|
// See chapter 12.4 "Image Layout" for details
|
|
|
|
|
|
|
|
|
|
void image_layout_transition(VkCommandBuffer command_buffer,
|
|
|
|
|
VkImage image,
|
|
|
|
|
VkPipelineStageFlags src_stage_mask,
|
|
|
|
|
VkPipelineStageFlags dst_stage_mask,
|
|
|
|
|
VkAccessFlags src_access_mask,
|
|
|
|
|
VkAccessFlags dst_access_mask,
|
|
|
|
|
VkImageLayout old_layout,
|
|
|
|
|
VkImageLayout new_layout,
|
|
|
|
|
VkImageSubresourceRange const &subresource_range)
|
|
|
|
|
{
|
|
|
|
|
// Create an image barrier object
|
|
|
|
|
VkImageMemoryBarrier image_memory_barrier{};
|
|
|
|
|
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
|
|
|
|
image_memory_barrier.srcAccessMask = src_access_mask;
|
|
|
|
|
image_memory_barrier.dstAccessMask = dst_access_mask;
|
|
|
|
|
image_memory_barrier.oldLayout = old_layout;
|
|
|
|
|
image_memory_barrier.newLayout = new_layout;
|
|
|
|
|
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
|
|
|
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
|
|
|
image_memory_barrier.image = image;
|
|
|
|
|
image_memory_barrier.subresourceRange = subresource_range;
|
|
|
|
|
|
|
|
|
|
// Put barrier inside setup command buffer
|
|
|
|
|
vkCmdPipelineBarrier(command_buffer, src_stage_mask, dst_stage_mask, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void image_layout_transition(VkCommandBuffer command_buffer,
|
|
|
|
|
VkImage image,
|
|
|
|
|
VkImageLayout old_layout,
|
|
|
|
|
VkImageLayout new_layout,
|
|
|
|
|
VkImageSubresourceRange const &subresource_range)
|
|
|
|
|
{
|
|
|
|
|
VkPipelineStageFlags src_stage_mask = getPipelineStageFlags(old_layout);
|
|
|
|
|
VkPipelineStageFlags dst_stage_mask = getPipelineStageFlags(new_layout);
|
|
|
|
|
VkAccessFlags src_access_mask = getAccessFlags(old_layout);
|
|
|
|
|
VkAccessFlags dst_access_mask = getAccessFlags(new_layout);
|
|
|
|
|
|
|
|
|
|
image_layout_transition(command_buffer, image, src_stage_mask, dst_stage_mask, src_access_mask, dst_access_mask, old_layout, new_layout, subresource_range);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fixed sub resource on first mip level and layer
|
|
|
|
|
void image_layout_transition(VkCommandBuffer command_buffer,
|
|
|
|
|
VkImage image,
|
|
|
|
|
VkImageLayout old_layout,
|
|
|
|
|
VkImageLayout new_layout)
|
|
|
|
|
{
|
|
|
|
|
VkImageSubresourceRange subresource_range = {};
|
|
|
|
|
subresource_range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
|
|
|
|
subresource_range.baseMipLevel = 0;
|
|
|
|
|
subresource_range.levelCount = 1;
|
|
|
|
|
subresource_range.baseArrayLayer = 0;
|
|
|
|
|
subresource_range.layerCount = 1;
|
|
|
|
|
image_layout_transition(command_buffer, image, old_layout, new_layout, subresource_range);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void image_layout_transition(VkCommandBuffer command_buffer,
|
|
|
|
|
std::vector<std::pair<VkImage, VkImageSubresourceRange>> const &imagesAndRanges,
|
|
|
|
|
VkImageLayout old_layout,
|
|
|
|
|
VkImageLayout new_layout)
|
|
|
|
|
{
|
|
|
|
|
VkPipelineStageFlags src_stage_mask = getPipelineStageFlags(old_layout);
|
|
|
|
|
VkPipelineStageFlags dst_stage_mask = getPipelineStageFlags(new_layout);
|
|
|
|
|
VkAccessFlags src_access_mask = getAccessFlags(old_layout);
|
|
|
|
|
VkAccessFlags dst_access_mask = getAccessFlags(new_layout);
|
|
|
|
|
|
|
|
|
|
// Create image barrier objects
|
|
|
|
|
std::vector<VkImageMemoryBarrier> image_memory_barriers;
|
|
|
|
|
image_memory_barriers.reserve(imagesAndRanges.size());
|
|
|
|
|
for (size_t i = 0; i < imagesAndRanges.size(); i++)
|
|
|
|
|
{
|
|
|
|
|
image_memory_barriers.emplace_back(VkImageMemoryBarrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
|
|
|
|
|
nullptr,
|
|
|
|
|
src_access_mask,
|
|
|
|
|
dst_access_mask,
|
|
|
|
|
old_layout,
|
|
|
|
|
new_layout,
|
|
|
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
|
|
|
VK_QUEUE_FAMILY_IGNORED,
|
|
|
|
|
imagesAndRanges[i].first,
|
|
|
|
|
imagesAndRanges[i].second});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Put barriers inside setup command buffer
|
|
|
|
|
vkCmdPipelineBarrier(command_buffer,
|
|
|
|
|
src_stage_mask,
|
|
|
|
|
dst_stage_mask,
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
nullptr,
|
|
|
|
|
0,
|
|
|
|
|
nullptr,
|
|
|
|
|
static_cast<uint32_t>(image_memory_barriers.size()),
|
|
|
|
|
image_memory_barriers.data());
|
|
|
|
|
}
|
2026-05-18 02:07:48 +02:00
|
|
|
}
|
|
|
|
|
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
|