7#include "gfx_pipeline_SDF_vulkan_intf.hpp"
8#include "gfx_surface_vulkan_intf.hpp"
9#include "gfx_device_vulkan_impl.hpp"
10#include "draw_context_intf.hpp"
11#include "../macros.hpp"
12#include <vulkan/vulkan.hpp>
14hi_export_module(hikogui.GFX : gfx_pipeline_SDF_impl);
16hi_export
namespace hi {
inline namespace v1 {
18inline void gfx_pipeline_SDF::draw_in_command_buffer(vk::CommandBuffer commandBuffer, draw_context
const& context)
20 gfx_pipeline::draw_in_command_buffer(commandBuffer, context);
22 hi_axiom_not_null(device());
23 device()->flushAllocation(vertexBufferAllocation, 0, vertexBufferData.size() *
sizeof(vertex));
27 hi_assert(tmpvertexBuffers.
size() == tmpOffsets.
size());
29 device()->SDF_pipeline->drawInCommandBuffer(commandBuffer);
31 commandBuffer.bindVertexBuffers(0, tmpvertexBuffers, tmpOffsets);
33 pushConstants.window_extent = extent2{narrow_cast<float>(extent.width), narrow_cast<float>(extent.height)};
34 pushConstants.viewport_scale = scale2{narrow_cast<float>(2.0f / extent.width), narrow_cast<float>(2.0f / extent.height)};
35 pushConstants.has_subpixels = context.subpixel_orientation != subpixel_orientation::unknown;
37 constexpr float third = 1.0f / 3.0f;
38 switch (context.subpixel_orientation) {
39 case subpixel_orientation::unknown:
40 pushConstants.red_subpixel_offset = vector2{0.0f, 0.0f};
41 pushConstants.blue_subpixel_offset = vector2{0.0f, 0.0f};
43 case subpixel_orientation::horizontal_rgb:
44 pushConstants.red_subpixel_offset = vector2{-third, 0.0f};
45 pushConstants.blue_subpixel_offset = vector2{third, 0.0f};
47 case subpixel_orientation::horizontal_bgr:
48 pushConstants.red_subpixel_offset = vector2{third, 0.0f};
49 pushConstants.blue_subpixel_offset = vector2{-third, 0.0f};
51 case subpixel_orientation::vertical_rgb:
52 pushConstants.red_subpixel_offset = vector2{0.0f, third};
53 pushConstants.blue_subpixel_offset = vector2{0.0f, -third};
55 case subpixel_orientation::vertical_bgr:
56 pushConstants.red_subpixel_offset = vector2{0.0f, -third};
57 pushConstants.blue_subpixel_offset = vector2{0.0f, third};
63 commandBuffer.pushConstants(
65 vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
67 sizeof(push_constants),
70 auto const numberOfRectangles = vertexBufferData.size() / 4;
71 auto const numberOfTriangles = numberOfRectangles * 2;
72 device()->cmdBeginDebugUtilsLabelEXT(commandBuffer,
"draw glyphs");
73 commandBuffer.drawIndexed(narrow_cast<uint32_t>(numberOfTriangles * 3), 1, 0, 0, 0);
74 device()->cmdEndDebugUtilsLabelEXT(commandBuffer);
79 hi_axiom_not_null(device());
80 return device()->SDF_pipeline->shaderStages;
87 bool has_dual_source_blend =
false;
88 if (
auto device_ = device()) {
89 has_dual_source_blend = device_->device_features.dualSrcBlend;
94 vk::BlendFactor::eOne,
95 has_dual_source_blend ? vk::BlendFactor::eOneMinusSrc1Color : vk::BlendFactor::eOneMinusSrcAlpha,
97 vk::BlendFactor::eOne,
98 has_dual_source_blend ? vk::BlendFactor::eOneMinusSrc1Alpha : vk::BlendFactor::eOneMinusSrcAlpha,
100 vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB |
101 vk::ColorComponentFlagBits::eA}};
108 vk::DescriptorType::eSampler,
110 vk::ShaderStageFlagBits::eFragment},
112 vk::DescriptorType::eSampledImage,
113 narrow_cast<uint32_t>(device_shared::atlasMaximumNrImages),
114 vk::ShaderStageFlagBits::eFragment}};
119 hi_axiom_not_null(device());
120 auto const& sharedImagePipeline = device()->SDF_pipeline;
128 vk::DescriptorType::eSampler,
129 &sharedImagePipeline->atlasSamplerDescriptorImageInfo,
137 narrow_cast<uint32_t>(device_shared::atlasMaximumNrImages),
138 vk::DescriptorType::eSampledImage,
139 sharedImagePipeline->atlasDescriptorImageInfos.data(),
146inline size_t gfx_pipeline_SDF::getDescriptorSetVersion()
const
148 hi_axiom_not_null(device());
149 return device()->SDF_pipeline->atlasTextures.size();
154 return push_constants::pushConstantRanges();
157inline vk::VertexInputBindingDescription gfx_pipeline_SDF::createVertexInputBindingDescription()
const
159 return vertex::inputBindingDescription();
164 return vertex::inputAttributeDescriptions();
167inline void gfx_pipeline_SDF::build_vertex_buffers()
169 using vertexIndexType = uint16_t;
170 constexpr ssize_t numberOfVertices = 1 << (
sizeof(vertexIndexType) * CHAR_BIT);
172 vk::BufferCreateInfo
const bufferCreateInfo = {
173 vk::BufferCreateFlags(),
174 sizeof(vertex) * numberOfVertices,
175 vk::BufferUsageFlagBits::eVertexBuffer,
176 vk::SharingMode::eExclusive};
177 VmaAllocationCreateInfo allocationCreateInfo = {};
178 allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
179 allocationCreateInfo.pUserData =
const_cast<char *
>(
"sdf-pipeline vertex buffer");
180 allocationCreateInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
182 hi_axiom_not_null(device());
183 std::tie(vertexBuffer, vertexBufferAllocation) = device()->createBuffer(bufferCreateInfo, allocationCreateInfo);
184 device()->setDebugUtilsObjectNameEXT(vertexBuffer,
"sdf-pipeline vertex buffer");
185 vertexBufferData = device()->mapMemory<vertex>(vertexBufferAllocation);
188inline void gfx_pipeline_SDF::teardown_vertex_buffers()
190 hi_axiom_not_null(device());
191 device()->unmapMemory(vertexBufferAllocation);
192 device()->destroyBuffer(vertexBuffer, vertexBufferAllocation);
195inline void gfx_pipeline_SDF::texture_map::transitionLayout(
const gfx_device &device, vk::Format format, vk::ImageLayout nextLayout)
199 if (layout != nextLayout) {
200 device.transition_layout(image, format, layout, nextLayout);
205inline gfx_pipeline_SDF::device_shared::device_shared(gfx_device
const& device) : device(device)
211inline gfx_pipeline_SDF::device_shared::~device_shared() {}
213inline void gfx_pipeline_SDF::device_shared::destroy(gfx_device
const *vulkanDevice)
215 hi_assert_not_null(vulkanDevice);
217 teardownShaders(vulkanDevice);
218 teardownAtlas(vulkanDevice);
221[[nodiscard]]
inline glyph_atlas_info gfx_pipeline_SDF::device_shared::allocate_rect(
extent2 draw_extent,
scale2 draw_scale)
noexcept
223 auto image_width = ceil_cast<int>(draw_extent.width());
224 auto image_height = ceil_cast<int>(draw_extent.height());
228 if (atlas_allocation_position.x() + image_width > atlasImageWidth) {
229 atlas_allocation_position.x() = 0;
230 atlas_allocation_position.y() = atlas_allocation_position.y() + atlasAllocationMaxHeight;
231 atlasAllocationMaxHeight = 0;
236 if (atlas_allocation_position.y() + image_height > atlasImageHeight) {
237 atlas_allocation_position.x() = 0;
238 atlas_allocation_position.y() = 0;
239 atlas_allocation_position.z() = atlas_allocation_position.z() + 1;
240 atlasAllocationMaxHeight = 0;
242 if (atlas_allocation_position.z() >= atlasMaximumNrImages) {
243 hi_log_fatal(
"gfx_pipeline_SDF atlas overflow, too many glyphs in use.");
246 if (atlas_allocation_position.z() >= atlasTextures.size()) {
251 auto r = glyph_atlas_info{atlas_allocation_position, draw_extent, draw_scale,
scale2{atlasTextureCoordinateMultiplier}};
252 atlas_allocation_position.x() = atlas_allocation_position.x() + image_width;
253 atlasAllocationMaxHeight =
std::max(atlasAllocationMaxHeight, image_height);
257inline void gfx_pipeline_SDF::device_shared::uploadStagingPixmapToAtlas(glyph_atlas_info
const& location)
260 device.flushAllocation(
261 stagingTexture.allocation, 0, (stagingTexture.pixmap.height() * stagingTexture.pixmap.stride()) *
sizeof(sdf_r8));
263 stagingTexture.transitionLayout(device, vk::Format::eR8Snorm, vk::ImageLayout::eTransferSrcOptimal);
268 {vk::ImageAspectFlagBits::eColor, 0, 0, 1},
270 {vk::ImageAspectFlagBits::eColor, 0, 0, 1},
271 {floor_cast<int32_t>(location.position.x()), floor_cast<int32_t>(location.position.y()), 0},
272 {ceil_cast<uint32_t>(location.size.width()), ceil_cast<uint32_t>(location.size.height()), 1}}};
274 auto& atlasTexture = atlasTextures.
at(floor_cast<std::size_t>(location.position.z()));
275 atlasTexture.transitionLayout(device, vk::Format::eR8Snorm, vk::ImageLayout::eTransferDstOptimal);
278 stagingTexture.image,
279 vk::ImageLayout::eTransferSrcOptimal,
281 vk::ImageLayout::eTransferDstOptimal,
285inline void gfx_pipeline_SDF::device_shared::prepareStagingPixmapForDrawing()
287 stagingTexture.transitionLayout(device, vk::Format::eR8Snorm, vk::ImageLayout::eGeneral);
290inline void gfx_pipeline_SDF::device_shared::prepare_atlas_for_rendering()
292 auto const lock = std::scoped_lock(gfx_system_mutex);
293 for (
auto& atlasTexture : atlasTextures) {
294 atlasTexture.transitionLayout(device, vk::Format::eR8Snorm, vk::ImageLayout::eShaderReadOnlyOptimal);
314inline void gfx_pipeline_SDF::device_shared::add_glyph_to_atlas(hi::font
const &font, glyph_id glyph, glyph_atlas_info& info)
noexcept
316 auto const glyph_metrics = font.get_metrics(glyph);
317 auto const glyph_path = font.get_path(glyph);
318 auto const glyph_bounding_box = glyph_metrics.bounding_rectangle;
320 auto const draw_scale =
scale2{drawfontSize, drawfontSize};
321 auto const draw_bounding_box = draw_scale * glyph_bounding_box;
328 auto const draw_offset = point2{drawBorder, drawBorder} - get<0>(draw_bounding_box);
329 auto const draw_extent = draw_bounding_box.size() + 2.0f * drawBorder;
330 auto const image_size = ceil(draw_extent);
333 auto const draw_path = (
translate2{draw_offset} * draw_scale) * glyph_path;
336 auto const lock = std::scoped_lock(gfx_system_mutex);
337 prepareStagingPixmapForDrawing();
338 info = allocate_rect(image_size, image_size / draw_bounding_box.size());
340 stagingTexture.pixmap.subimage(0, 0, ceil_cast<size_t>(info.size.width()), ceil_cast<size_t>(info.size.height()));
341 fill(pixmap, draw_path);
342 uploadStagingPixmapToAtlas(info);
345inline bool gfx_pipeline_SDF::device_shared::place_vertices(
346 vector_span<vertex>& vertices,
349 hi::font
const &font, glyph_id glyph,
352 auto const[atlas_rect, glyph_was_added] = this->get_glyph_from_atlas(font, glyph);
356 auto image_index = atlas_rect->position.z();
357 auto t0 = point3(get<0>(atlas_rect->texture_coordinates), image_index);
358 auto t1 = point3(get<1>(atlas_rect->texture_coordinates), image_index);
359 auto t2 = point3(get<2>(atlas_rect->texture_coordinates), image_index);
360 auto t3 = point3(get<3>(atlas_rect->texture_coordinates), image_index);
362 vertices.emplace_back(box_with_border.p0, clipping_rectangle, t0, colors.p0);
363 vertices.emplace_back(box_with_border.p1, clipping_rectangle, t1, colors.p1);
364 vertices.emplace_back(box_with_border.p2, clipping_rectangle, t2, colors.p2);
365 vertices.emplace_back(box_with_border.p3, clipping_rectangle, t3, colors.p3);
366 return glyph_was_added;
369inline void gfx_pipeline_SDF::device_shared::drawInCommandBuffer(vk::CommandBuffer
const& commandBuffer)
371 commandBuffer.bindIndexBuffer(device.quadIndexBuffer, 0, vk::IndexType::eUint16);
374inline void gfx_pipeline_SDF::device_shared::buildShaders()
376 specializationConstants.sdf_r8maxDistance = sdf_r8::max_distance;
377 specializationConstants.atlasImageWidth = atlasImageWidth;
379 fragmentShaderSpecializationMapEntries = specialization_constants::specializationConstantMapEntries();
380 fragmentShaderSpecializationInfo = specializationConstants.specializationInfo(fragmentShaderSpecializationMapEntries);
382 vertexShaderModule = device.loadShader(
URL(
"resource:SDF_vulkan.vert.spv"));
383 fragmentShaderModule = device.loadShader(
URL(
"resource:SDF_vulkan.frag.spv"));
386 {vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eVertex, vertexShaderModule,
"main"},
387 {vk::PipelineShaderStageCreateFlags(),
388 vk::ShaderStageFlagBits::eFragment,
389 fragmentShaderModule,
391 &fragmentShaderSpecializationInfo}};
394inline void gfx_pipeline_SDF::device_shared::teardownShaders(gfx_device
const *vulkanDevice)
396 hi_assert_not_null(vulkanDevice);
398 vulkanDevice->destroy(vertexShaderModule);
399 vulkanDevice->destroy(fragmentShaderModule);
402inline void gfx_pipeline_SDF::device_shared::addAtlasImage()
404 auto const current_image_index = atlasTextures.size();
407 vk::ImageCreateInfo
const imageCreateInfo = {
408 vk::ImageCreateFlags(),
410 vk::Format::eR8Snorm,
411 vk::Extent3D(atlasImageWidth, atlasImageHeight, 1),
414 vk::SampleCountFlagBits::e1,
415 vk::ImageTiling::eOptimal,
416 vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled,
417 vk::SharingMode::eExclusive,
420 vk::ImageLayout::eUndefined};
421 VmaAllocationCreateInfo allocationCreateInfo = {};
422 auto allocation_name = std::format(
"sdf-pipeline atlas image {}", current_image_index);
423 allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
424 allocationCreateInfo.pUserData =
const_cast<char *
>(allocation_name.c_str());
425 allocationCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
427 auto const[atlasImage, atlasImageAllocation] = device.createImage(imageCreateInfo, allocationCreateInfo);
428 device.setDebugUtilsObjectNameEXT(atlasImage, allocation_name.c_str());
430 auto const clearValue = vk::ClearColorValue{
std::array{-1.0f, -1.0f, -1.0f, -1.0f}};
431 auto const clearRange =
std::array{vk::ImageSubresourceRange{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}};
433 device.transition_layout(
434 atlasImage, imageCreateInfo.format, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal);
435 device.clearColorImage(atlasImage, vk::ImageLayout::eTransferDstOptimal, clearValue, clearRange);
437 auto const atlasImageView = device.createImageView(
438 {vk::ImageViewCreateFlags(),
440 vk::ImageViewType::e2D,
441 imageCreateInfo.format,
442 vk::ComponentMapping(),
444 vk::ImageAspectFlagBits::eColor,
451 atlasTextures.push_back({atlasImage, atlasImageAllocation, atlasImageView});
454 for (
int i = 0; i < ssize(atlasDescriptorImageInfos); i++) {
457 atlasDescriptorImageInfos.
at(i) = {
459 i < atlasTextures.size() ? atlasTextures.at(i).view : atlasTextures.at(0).view,
460 vk::ImageLayout::eShaderReadOnlyOptimal};
464inline void gfx_pipeline_SDF::device_shared::buildAtlas()
467 vk::ImageCreateInfo
const imageCreateInfo = {
468 vk::ImageCreateFlags(),
470 vk::Format::eR8Snorm,
471 vk::Extent3D(stagingImageWidth, stagingImageHeight, 1),
474 vk::SampleCountFlagBits::e1,
475 vk::ImageTiling::eLinear,
476 vk::ImageUsageFlagBits::eTransferSrc,
477 vk::SharingMode::eExclusive,
480 vk::ImageLayout::ePreinitialized};
481 VmaAllocationCreateInfo allocationCreateInfo = {};
482 allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
483 allocationCreateInfo.pUserData =
const_cast<char *
>(
"sdf-pipeline staging image");
484 allocationCreateInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
485 auto const[image, allocation] = device.createImage(imageCreateInfo, allocationCreateInfo);
486 device.setDebugUtilsObjectNameEXT(image,
"sdf-pipeline staging image");
487 auto const data = device.mapMemory<sdf_r8>(allocation);
495 vk::SamplerCreateInfo
const samplerCreateInfo = {
496 vk::SamplerCreateFlags(),
499 vk::SamplerMipmapMode::eNearest,
500 vk::SamplerAddressMode::eClampToEdge,
501 vk::SamplerAddressMode::eClampToEdge,
502 vk::SamplerAddressMode::eClampToEdge,
507 vk::CompareOp::eNever,
510 vk::BorderColor::eFloatTransparentBlack,
513 atlasSampler = device.createSampler(samplerCreateInfo);
514 device.setDebugUtilsObjectNameEXT(atlasSampler,
"sdf-pipeline atlas sampler");
516 atlasSamplerDescriptorImageInfo = {atlasSampler, vk::ImageView(), vk::ImageLayout::eUndefined};
523inline void gfx_pipeline_SDF::device_shared::teardownAtlas(gfx_device
const *vulkanDevice)
525 hi_assert_not_null(vulkanDevice);
527 vulkanDevice->destroy(atlasSampler);
529 for (
const auto& atlasImage : atlasTextures) {
530 vulkanDevice->destroy(atlasImage.view);
531 vulkanDevice->destroyImage(atlasImage.image, atlasImage.allocation);
533 atlasTextures.clear();
535 vulkanDevice->unmapMemory(stagingTexture.allocation);
536 vulkanDevice->destroyImage(stagingTexture.image, stagingTexture.allocation);
The HikoGUI namespace.
Definition array_generic.hpp:20
std::ptrdiff_t ssize_t
Signed size/index into an array.
Definition misc.hpp:32
constexpr quad scale_from_center(quad const &lhs, scale2 const &rhs) noexcept
scale the quad.
Definition transform.hpp:463
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
unfair_recursive_mutex gfx_system_mutex
Global mutex for GUI elements, like gfx_system, gfx_device, Windows and Widgets.
Definition gfx_system_globals.hpp:18
A color for each corner of a quad.
Definition quad_color.hpp:22
Class which represents an axis-aligned rectangle.
Definition aarectangle.hpp:33
A high-level geometric extent.
Definition extent2.hpp:32
Definition translate2.hpp:18
A non-owning 2D pixel-based image.
Definition pixmap_span.hpp:34
Universal Resource Locator.
Definition URL.hpp:58