HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
gfx_pipeline_SDF_vulkan_impl.hpp
1// Copyright Take Vos 2020-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "gfx_pipeline_SDF_vulkan.hpp"
8#include "gfx_surface_vulkan.hpp"
9#include "gfx_device_vulkan_impl.hpp"
10#include "draw_context.hpp"
11#include "../macros.hpp"
12
13namespace hi { inline namespace v1 {
14
15inline void gfx_pipeline_SDF::draw_in_command_buffer(vk::CommandBuffer commandBuffer, draw_context const& context)
16{
17 gfx_pipeline::draw_in_command_buffer(commandBuffer, context);
18
19 hi_axiom_not_null(device());
20 device()->flushAllocation(vertexBufferAllocation, 0, vertexBufferData.size() * sizeof(vertex));
21
24 hi_assert(tmpvertexBuffers.size() == tmpOffsets.size());
25
26 device()->SDF_pipeline->drawInCommandBuffer(commandBuffer);
27
28 commandBuffer.bindVertexBuffers(0, tmpvertexBuffers, tmpOffsets);
29
30 pushConstants.window_extent = extent2{narrow_cast<float>(extent.width), narrow_cast<float>(extent.height)};
31 pushConstants.viewport_scale = scale2{narrow_cast<float>(2.0f / extent.width), narrow_cast<float>(2.0f / extent.height)};
32 pushConstants.has_subpixels = context.subpixel_orientation != subpixel_orientation::unknown;
33
34 constexpr float third = 1.0f / 3.0f;
35 switch (context.subpixel_orientation) {
36 case subpixel_orientation::unknown:
37 pushConstants.red_subpixel_offset = vector2{0.0f, 0.0f};
38 pushConstants.blue_subpixel_offset = vector2{0.0f, 0.0f};
39 break;
40 case subpixel_orientation::horizontal_rgb:
41 pushConstants.red_subpixel_offset = vector2{-third, 0.0f};
42 pushConstants.blue_subpixel_offset = vector2{third, 0.0f};
43 break;
44 case subpixel_orientation::horizontal_bgr:
45 pushConstants.red_subpixel_offset = vector2{third, 0.0f};
46 pushConstants.blue_subpixel_offset = vector2{-third, 0.0f};
47 break;
48 case subpixel_orientation::vertical_rgb:
49 pushConstants.red_subpixel_offset = vector2{0.0f, third};
50 pushConstants.blue_subpixel_offset = vector2{0.0f, -third};
51 break;
52 case subpixel_orientation::vertical_bgr:
53 pushConstants.red_subpixel_offset = vector2{0.0f, -third};
54 pushConstants.blue_subpixel_offset = vector2{0.0f, third};
55 break;
56 default:
57 hi_no_default();
58 }
59
60 commandBuffer.pushConstants(
61 pipelineLayout,
62 vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
63 0,
64 sizeof(push_constants),
65 &pushConstants);
66
67 hilet numberOfRectangles = vertexBufferData.size() / 4;
69 device()->cmdBeginDebugUtilsLabelEXT(commandBuffer, "draw glyphs");
70 commandBuffer.drawIndexed(narrow_cast<uint32_t>(numberOfTriangles * 3), 1, 0, 0, 0);
71 device()->cmdEndDebugUtilsLabelEXT(commandBuffer);
72}
73
74inline std::vector<vk::PipelineShaderStageCreateInfo> gfx_pipeline_SDF::createShaderStages() const
75{
76 hi_axiom_not_null(device());
77 return device()->SDF_pipeline->shaderStages;
78}
79
80/* Dual-source alpha blending which allows subpixel anti-aliasing.
81 */
82inline std::vector<vk::PipelineColorBlendAttachmentState> gfx_pipeline_SDF::getPipelineColorBlendAttachmentStates() const
83{
84 bool has_dual_source_blend = false;
85 if (auto device_ = device()) {
86 has_dual_source_blend = device_->device_features.dualSrcBlend;
87 }
88
89 return {
90 {VK_TRUE, // blendEnable
91 vk::BlendFactor::eOne, // srcColorBlendFactor
92 has_dual_source_blend ? vk::BlendFactor::eOneMinusSrc1Color : vk::BlendFactor::eOneMinusSrcAlpha, // dstColorBlendFactor
93 vk::BlendOp::eAdd, // colorBlendOp
94 vk::BlendFactor::eOne, // srcAlphaBlendFactor
95 has_dual_source_blend ? vk::BlendFactor::eOneMinusSrc1Alpha : vk::BlendFactor::eOneMinusSrcAlpha, // dstAlphaBlendFactor
96 vk::BlendOp::eAdd, // aphaBlendOp
97 vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB |
98 vk::ColorComponentFlagBits::eA}};
99}
100
101inline std::vector<vk::DescriptorSetLayoutBinding> gfx_pipeline_SDF::createDescriptorSetLayoutBindings() const
102{
103 return {
104 {0, // binding
105 vk::DescriptorType::eSampler,
106 1, // descriptorCount
107 vk::ShaderStageFlagBits::eFragment},
108 {1, // binding
109 vk::DescriptorType::eSampledImage,
110 narrow_cast<uint32_t>(device_shared::atlasMaximumNrImages), // descriptorCount
111 vk::ShaderStageFlagBits::eFragment}};
112}
113
114inline std::vector<vk::WriteDescriptorSet> gfx_pipeline_SDF::createWriteDescriptorSet() const
115{
116 hi_axiom_not_null(device());
117 hilet& sharedImagePipeline = device()->SDF_pipeline;
118
119 return {
120 {
121 descriptorSet,
122 0, // destBinding
123 0, // arrayElement
124 1, // descriptorCount
125 vk::DescriptorType::eSampler,
126 &sharedImagePipeline->atlasSamplerDescriptorImageInfo,
127 nullptr, // bufferInfo
128 nullptr // texelBufferView
129 },
130 {
131 descriptorSet,
132 1, // destBinding
133 0, // arrayElement
134 narrow_cast<uint32_t>(device_shared::atlasMaximumNrImages), // descriptorCount
135 vk::DescriptorType::eSampledImage,
136 sharedImagePipeline->atlasDescriptorImageInfos.data(),
137 nullptr, // bufferInfo
138 nullptr // texelBufferView
139 },
140 };
141}
142
143inline size_t gfx_pipeline_SDF::getDescriptorSetVersion() const
144{
145 hi_axiom_not_null(device());
146 return device()->SDF_pipeline->atlasTextures.size();
147}
148
149inline std::vector<vk::PushConstantRange> gfx_pipeline_SDF::createPushConstantRanges() const
150{
151 return push_constants::pushConstantRanges();
152}
153
154inline vk::VertexInputBindingDescription gfx_pipeline_SDF::createVertexInputBindingDescription() const
155{
156 return vertex::inputBindingDescription();
157}
158
159inline std::vector<vk::VertexInputAttributeDescription> gfx_pipeline_SDF::createVertexInputAttributeDescriptions() const
160{
161 return vertex::inputAttributeDescriptions();
162}
163
164inline void gfx_pipeline_SDF::build_vertex_buffers()
165{
166 using vertexIndexType = uint16_t;
167 constexpr ssize_t numberOfVertices = 1 << (sizeof(vertexIndexType) * CHAR_BIT);
168
169 vk::BufferCreateInfo const bufferCreateInfo = {
170 vk::BufferCreateFlags(),
171 sizeof(vertex) * numberOfVertices,
172 vk::BufferUsageFlagBits::eVertexBuffer,
173 vk::SharingMode::eExclusive};
176 allocationCreateInfo.pUserData = const_cast<char *>("sdf-pipeline vertex buffer");
178
179 hi_axiom_not_null(device());
180 std::tie(vertexBuffer, vertexBufferAllocation) = device()->createBuffer(bufferCreateInfo, allocationCreateInfo);
181 device()->setDebugUtilsObjectNameEXT(vertexBuffer, "sdf-pipeline vertex buffer");
182 vertexBufferData = device()->mapMemory<vertex>(vertexBufferAllocation);
183}
184
185inline void gfx_pipeline_SDF::teardown_vertex_buffers()
186{
187 hi_axiom_not_null(device());
188 device()->unmapMemory(vertexBufferAllocation);
189 device()->destroyBuffer(vertexBuffer, vertexBufferAllocation);
190}
191
192inline void gfx_pipeline_SDF::texture_map::transitionLayout(const gfx_device &device, vk::Format format, vk::ImageLayout nextLayout)
193{
194 hi_axiom(gfx_system_mutex.recurse_lock_count());
195
196 if (layout != nextLayout) {
197 device.transition_layout(image, format, layout, nextLayout);
198 layout = nextLayout;
199 }
200}
201
202inline gfx_pipeline_SDF::device_shared::device_shared(gfx_device const& device) : device(device)
203{
204 buildShaders();
205 buildAtlas();
206}
207
208inline gfx_pipeline_SDF::device_shared::~device_shared() {}
209
210inline void gfx_pipeline_SDF::device_shared::destroy(gfx_device const *vulkanDevice)
211{
212 hi_assert_not_null(vulkanDevice);
213
214 teardownShaders(vulkanDevice);
215 teardownAtlas(vulkanDevice);
216}
217
218[[nodiscard]] inline glyph_atlas_info gfx_pipeline_SDF::device_shared::allocate_rect(extent2 draw_extent, scale2 draw_scale) noexcept
219{
220 auto image_width = ceil_cast<int>(draw_extent.width());
221 auto image_height = ceil_cast<int>(draw_extent.height());
222
223 // Check if the glyph still fits in the same line of glyphs.
224 // Otherwise go to the next line.
225 if (atlas_allocation_position.x() + image_width > atlasImageWidth) {
226 atlas_allocation_position.x() = 0;
227 atlas_allocation_position.y() = atlas_allocation_position.y() + atlasAllocationMaxHeight;
228 atlasAllocationMaxHeight = 0;
229 }
230
231 // Check if the glyph still fits in the image.
232 // Otherwise allocate a new image.
233 if (atlas_allocation_position.y() + image_height > atlasImageHeight) {
234 atlas_allocation_position.x() = 0;
235 atlas_allocation_position.y() = 0;
236 atlas_allocation_position.z() = atlas_allocation_position.z() + 1;
237 atlasAllocationMaxHeight = 0;
238
239 if (atlas_allocation_position.z() >= atlasMaximumNrImages) {
240 hi_log_fatal("gfx_pipeline_SDF atlas overflow, too many glyphs in use.");
241 }
242
243 if (atlas_allocation_position.z() >= atlasTextures.size()) {
244 addAtlasImage();
245 }
246 }
247
248 auto r = glyph_atlas_info{atlas_allocation_position, draw_extent, draw_scale, scale2{atlasTextureCoordinateMultiplier}};
249 atlas_allocation_position.x() = atlas_allocation_position.x() + image_width;
250 atlasAllocationMaxHeight = std::max(atlasAllocationMaxHeight, image_height);
251 return r;
252}
253
254inline void gfx_pipeline_SDF::device_shared::uploadStagingPixmapToAtlas(glyph_atlas_info const& location)
255{
256 // Flush the given image, included the border.
257 device.flushAllocation(
258 stagingTexture.allocation, 0, (stagingTexture.pixmap.height() * stagingTexture.pixmap.stride()) * sizeof(sdf_r8));
259
260 stagingTexture.transitionLayout(device, vk::Format::eR8Snorm, vk::ImageLayout::eTransferSrcOptimal);
261
262 std::array<std::vector<vk::ImageCopy>, atlasMaximumNrImages> regionsToCopyPerAtlasTexture;
263
264 auto regionsToCopy = std::vector{vk::ImageCopy{
265 {vk::ImageAspectFlagBits::eColor, 0, 0, 1},
266 {0, 0, 0},
267 {vk::ImageAspectFlagBits::eColor, 0, 0, 1},
268 {floor_cast<int32_t>(location.position.x()), floor_cast<int32_t>(location.position.y()), 0},
269 {ceil_cast<uint32_t>(location.size.width()), ceil_cast<uint32_t>(location.size.height()), 1}}};
270
271 auto& atlasTexture = atlasTextures.at(floor_cast<std::size_t>(location.position.z()));
272 atlasTexture.transitionLayout(device, vk::Format::eR8Snorm, vk::ImageLayout::eTransferDstOptimal);
273
274 device.copyImage(
275 stagingTexture.image,
276 vk::ImageLayout::eTransferSrcOptimal,
277 atlasTexture.image,
278 vk::ImageLayout::eTransferDstOptimal,
279 std::move(regionsToCopy));
280}
281
282inline void gfx_pipeline_SDF::device_shared::prepareStagingPixmapForDrawing()
283{
284 stagingTexture.transitionLayout(device, vk::Format::eR8Snorm, vk::ImageLayout::eGeneral);
285}
286
287inline void gfx_pipeline_SDF::device_shared::prepare_atlas_for_rendering()
288{
289 hilet lock = std::scoped_lock(gfx_system_mutex);
290 for (auto& atlasTexture : atlasTextures) {
291 atlasTexture.transitionLayout(device, vk::Format::eR8Snorm, vk::ImageLayout::eShaderReadOnlyOptimal);
292 }
293}
294
311inline void gfx_pipeline_SDF::device_shared::add_glyph_to_atlas(hi::font const &font, glyph_id glyph, glyph_atlas_info& info) noexcept
312{
313 hilet glyph_metrics = font.get_metrics(glyph);
314 hilet glyph_path = font.get_path(glyph);
315 hilet glyph_bounding_box = glyph_metrics.bounding_rectangle;
316
317 hilet draw_scale = scale2{drawfontSize, drawfontSize};
318 hilet draw_bounding_box = draw_scale * glyph_bounding_box;
319
320 // We will draw the font at a fixed size into the texture. And we need a border for the texture to
321 // allow proper bi-linear interpolation on the edges.
322
323 // Determine the size of the image in the atlas.
324 // This is the bounding box sized to the fixed font size and a border
325 hilet draw_offset = point2{drawBorder, drawBorder} - get<0>(draw_bounding_box);
326 hilet draw_extent = draw_bounding_box.size() + 2.0f * drawBorder;
327 hilet image_size = ceil(draw_extent);
328
329 // Transform the path to the scale of the fixed font size and drawing the bounding box inside the image.
330 hilet draw_path = (translate2{draw_offset} * draw_scale) * glyph_path;
331
332 // Draw glyphs into staging buffer of the atlas and upload it to the correct position in the atlas.
333 hilet lock = std::scoped_lock(gfx_system_mutex);
334 prepareStagingPixmapForDrawing();
335 info = allocate_rect(image_size, image_size / draw_bounding_box.size());
336 auto pixmap =
337 stagingTexture.pixmap.subimage(0, 0, ceil_cast<size_t>(info.size.width()), ceil_cast<size_t>(info.size.height()));
338 fill(pixmap, draw_path);
339 uploadStagingPixmapToAtlas(info);
340}
341
342inline bool gfx_pipeline_SDF::device_shared::place_vertices(
343 vector_span<vertex>& vertices,
344 aarectangle const& clipping_rectangle,
345 quad const& box,
346 hi::font const &font, glyph_id glyph,
347 quad_color colors) noexcept
348{
349 hilet[atlas_rect, glyph_was_added] = this->get_glyph_from_atlas(font, glyph);
350
351 hilet box_with_border = scale_from_center(box, atlas_rect->border_scale);
352
353 auto image_index = atlas_rect->position.z();
354 auto t0 = point3(get<0>(atlas_rect->texture_coordinates), image_index);
355 auto t1 = point3(get<1>(atlas_rect->texture_coordinates), image_index);
356 auto t2 = point3(get<2>(atlas_rect->texture_coordinates), image_index);
357 auto t3 = point3(get<3>(atlas_rect->texture_coordinates), image_index);
358
359 vertices.emplace_back(box_with_border.p0, clipping_rectangle, t0, colors.p0);
360 vertices.emplace_back(box_with_border.p1, clipping_rectangle, t1, colors.p1);
361 vertices.emplace_back(box_with_border.p2, clipping_rectangle, t2, colors.p2);
362 vertices.emplace_back(box_with_border.p3, clipping_rectangle, t3, colors.p3);
363 return glyph_was_added;
364}
365
366inline void gfx_pipeline_SDF::device_shared::drawInCommandBuffer(vk::CommandBuffer const& commandBuffer)
367{
368 commandBuffer.bindIndexBuffer(device.quadIndexBuffer, 0, vk::IndexType::eUint16);
369}
370
371inline void gfx_pipeline_SDF::device_shared::buildShaders()
372{
373 specializationConstants.sdf_r8maxDistance = sdf_r8::max_distance;
374 specializationConstants.atlasImageWidth = atlasImageWidth;
375
376 fragmentShaderSpecializationMapEntries = specialization_constants::specializationConstantMapEntries();
377 fragmentShaderSpecializationInfo = specializationConstants.specializationInfo(fragmentShaderSpecializationMapEntries);
378
379 vertexShaderModule = device.loadShader(URL("resource:SDF_vulkan.vert.spv"));
380 fragmentShaderModule = device.loadShader(URL("resource:SDF_vulkan.frag.spv"));
381
382 shaderStages = {
383 {vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eVertex, vertexShaderModule, "main"},
384 {vk::PipelineShaderStageCreateFlags(),
385 vk::ShaderStageFlagBits::eFragment,
386 fragmentShaderModule,
387 "main",
388 &fragmentShaderSpecializationInfo}};
389}
390
391inline void gfx_pipeline_SDF::device_shared::teardownShaders(gfx_device const *vulkanDevice)
392{
393 hi_assert_not_null(vulkanDevice);
394
395 vulkanDevice->destroy(vertexShaderModule);
396 vulkanDevice->destroy(fragmentShaderModule);
397}
398
399inline void gfx_pipeline_SDF::device_shared::addAtlasImage()
400{
401 hilet current_image_index = atlasTextures.size();
402
403 // Create atlas image
404 vk::ImageCreateInfo const imageCreateInfo = {
405 vk::ImageCreateFlags(),
406 vk::ImageType::e2D,
407 vk::Format::eR8Snorm,
408 vk::Extent3D(atlasImageWidth, atlasImageHeight, 1),
409 1, // mipLevels
410 1, // arrayLayers
411 vk::SampleCountFlagBits::e1,
412 vk::ImageTiling::eOptimal,
413 vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled,
414 vk::SharingMode::eExclusive,
415 0,
416 nullptr,
417 vk::ImageLayout::eUndefined};
418 VmaAllocationCreateInfo allocationCreateInfo = {};
419 auto allocation_name = std::format("sdf-pipeline atlas image {}", current_image_index);
420 allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
421 allocationCreateInfo.pUserData = const_cast<char *>(allocation_name.c_str());
422 allocationCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
423
424 hilet[atlasImage, atlasImageAllocation] = device.createImage(imageCreateInfo, allocationCreateInfo);
425 device.setDebugUtilsObjectNameEXT(atlasImage, allocation_name.c_str());
426
427 hilet clearValue = vk::ClearColorValue{std::array{-1.0f, -1.0f, -1.0f, -1.0f}};
428 hilet clearRange = std::array{vk::ImageSubresourceRange{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}};
429
430 device.transition_layout(
431 atlasImage, imageCreateInfo.format, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal);
432 device.clearColorImage(atlasImage, vk::ImageLayout::eTransferDstOptimal, clearValue, clearRange);
433
434 hilet atlasImageView = device.createImageView(
435 {vk::ImageViewCreateFlags(),
436 atlasImage,
437 vk::ImageViewType::e2D,
438 imageCreateInfo.format,
439 vk::ComponentMapping(),
440 {
441 vk::ImageAspectFlagBits::eColor,
442 0, // baseMipLevel
443 1, // levelCount
444 0, // baseArrayLayer
445 1 // layerCount
446 }});
447
448 atlasTextures.push_back({atlasImage, atlasImageAllocation, atlasImageView});
449
450 // Build image descriptor info.
451 for (int i = 0; i < ssize(atlasDescriptorImageInfos); i++) {
452 // Point the descriptors to each imageView,
453 // repeat the first imageView if there are not enough.
454 atlasDescriptorImageInfos.at(i) = {
455 vk::Sampler(),
456 i < atlasTextures.size() ? atlasTextures.at(i).view : atlasTextures.at(0).view,
457 vk::ImageLayout::eShaderReadOnlyOptimal};
458 }
459}
460
461inline void gfx_pipeline_SDF::device_shared::buildAtlas()
462{
463 // Create staging image
464 vk::ImageCreateInfo const imageCreateInfo = {
465 vk::ImageCreateFlags(),
466 vk::ImageType::e2D,
467 vk::Format::eR8Snorm,
468 vk::Extent3D(stagingImageWidth, stagingImageHeight, 1),
469 1, // mipLevels
470 1, // arrayLayers
471 vk::SampleCountFlagBits::e1,
472 vk::ImageTiling::eLinear,
473 vk::ImageUsageFlagBits::eTransferSrc,
474 vk::SharingMode::eExclusive,
475 0,
476 nullptr,
477 vk::ImageLayout::ePreinitialized};
478 VmaAllocationCreateInfo allocationCreateInfo = {};
479 allocationCreateInfo.flags = VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT;
480 allocationCreateInfo.pUserData = const_cast<char *>("sdf-pipeline staging image");
481 allocationCreateInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
482 hilet[image, allocation] = device.createImage(imageCreateInfo, allocationCreateInfo);
483 device.setDebugUtilsObjectNameEXT(image, "sdf-pipeline staging image");
484 hilet data = device.mapMemory<sdf_r8>(allocation);
485
486 stagingTexture = {
487 image,
488 allocation,
489 vk::ImageView(),
490 hi::pixmap_span<sdf_r8>{data.data(), imageCreateInfo.extent.width, imageCreateInfo.extent.height}};
491
492 vk::SamplerCreateInfo const samplerCreateInfo = {
493 vk::SamplerCreateFlags(),
494 vk::Filter::eLinear, // magFilter
495 vk::Filter::eLinear, // minFilter
496 vk::SamplerMipmapMode::eNearest, // mipmapMode
497 vk::SamplerAddressMode::eClampToEdge, // addressModeU
498 vk::SamplerAddressMode::eClampToEdge, // addressModeV
499 vk::SamplerAddressMode::eClampToEdge, // addressModeW
500 0.0, // mipLodBias
501 VK_FALSE, // anisotropyEnable
502 0.0, // maxAnisotropy
503 VK_FALSE, // compareEnable
504 vk::CompareOp::eNever,
505 0.0, // minLod
506 0.0, // maxLod
507 vk::BorderColor::eFloatTransparentBlack,
508 VK_FALSE // unnormazlizedCoordinates
509 };
510 atlasSampler = device.createSampler(samplerCreateInfo);
511 device.setDebugUtilsObjectNameEXT(atlasSampler, "sdf-pipeline atlas sampler");
512
513 atlasSamplerDescriptorImageInfo = {atlasSampler, vk::ImageView(), vk::ImageLayout::eUndefined};
514
515 // There needs to be at least one atlas image, so the array of samplers can point to
516 // the single image.
517 addAtlasImage();
518}
519
520inline void gfx_pipeline_SDF::device_shared::teardownAtlas(gfx_device const *vulkanDevice)
521{
522 hi_assert_not_null(vulkanDevice);
523
524 vulkanDevice->destroy(atlasSampler);
525
526 for (const auto& atlasImage : atlasTextures) {
527 vulkanDevice->destroy(atlasImage.view);
528 vulkanDevice->destroyImage(atlasImage.image, atlasImage.allocation);
529 }
530 atlasTextures.clear();
531
532 vulkanDevice->unmapMemory(stagingTexture.allocation);
533 vulkanDevice->destroyImage(stagingTexture.image, stagingTexture.allocation);
534}
535
536}} // namespace hi::inline v1::gfx_pipeline_SDF
DOXYGEN BUG.
Definition algorithm.hpp:16
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
geometry/margins.hpp
Definition lookahead_iterator.hpp:5
std::ptrdiff_t ssize_t
Signed size/index into an array.
Definition misc.hpp:33
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:377
constexpr quad scale_from_center(quad const &lhs, scale2 const &rhs) noexcept
scale the quad.
Definition transform.hpp:459
A color for each corner of a quad.
Definition quad_color.hpp:20
Class which represents an axis-aligned rectangle.
Definition aarectangle.hpp:29
A high-level geometric extent.
Definition extent2.hpp:29
Definition scale2.hpp:13
Definition translate2.hpp:14
A non-owning 2D pixel-based image.
Definition pixmap_span.hpp:34
Universal Resource Locator.
Definition URL.hpp:51
T at(T... args)
T max(T... args)
T move(T... args)
T tie(T... args)