Upgrade to Pro — share decks privately, control downloads, hide ads and more …

低レイヤーから始める GUI

Fadis
January 15, 2023

低レイヤーから始める GUI

QtとかGTK+とかXとかWaylandとかそういうものに頼らないでLinux上でGUIする方法を解説します
これは2023年1月15日に行われた カーネル/VM探検隊 online part6での発表資料です

発表動画: https://youtu.be/nOLjuPb_dPo
ソースコード: https://github.com/Fadis/gct/tree/kernelvm_20230115

Fadis

January 15, 2023
Tweet

More Decks by Fadis

Other Decks in Programming

Transcript

  1. GPU

  2. Χʔωϧۭؒ DRM/KMS Ϣʔβۭؒ Ϣʔβۭؒ Ϣʔβۭؒ εέδϡʔϥ Ϩϯμʔϊʔυ ܭࢉ දࣔ LinuxͰͷ

    GPUͷѻ͍ Linux ϓϥΠϚϦϊʔυ Ϩϯμʔϊʔυ /dev/dri/renderD128ͱ͔ GPUͷϝϞϦΛ֬อͰ͖Δ GPUͰܭࢉͰ͖Δ දࣔ͸Ͱ͖ͳ͍ ͜ͷσόΠεΛհͯ͠ ෳ਺ͷϓϩηε͕ಉ࣌ʹ ͜ͷσόΠεΛ։͚Δ
  3. ϧۭؒ DRM/KMS Ϣʔβۭؒ Ϣʔβۭؒ Ϣʔβۭؒ εέδϡʔϥ Ϩϯμʔϊʔυ ܭࢉ දࣔ uxͰͷ

    Uͷѻ͍ ux ϓϥΠϚϦϊʔυ ϓϥΠϚϦϊʔυ /dev/dri/card0ͱ͔ GPUͷϝϞϦΛ֬อͰ͖Δ GPUͰܭࢉͰ͖Δ දࣔ΋Ͱ͖Δ ͜ͷσόΠεΛհͯ͠ ಉ࣌ʹ͜ͷσόΠεΛ ։͚Δϓϩηε͸1͚ͭͩ
  4. ϏσΦϝϞϦ Χʔωϧۭؒ Ϣʔβۭؒ Ϣʔβۭؒ Ϣʔβۭؒ ද ࣔ Linux αʔό ΫϥΠΞϯτ

    ΫϥΠΞϯτ ॻ ͘ ಡ Ή Ϩϯμʔϊʔυ ಡ Ή ॻ ͘ ॻ ͘ ϓϥΠϚϦϊʔυ ͓લɺڞ༗ϝϞϦʹॻ͘ Զɺڞ༗ϝϞϦΛಡΉ ॻ͍ͨ Window System
  5. ϏσΦϝϞϦ Χʔωϧۭؒ Ϣʔβۭؒ Ϣʔβۭؒ Ϣʔβۭؒ ද ࣔ Linux Wayland ίϯϙδλ

    Wayland ΫϥΠΞϯτ ॻ ͘ ಡ Ή Ϩϯμʔϊʔυ ಡ Ή ॻ ͘ ॻ ͘ ϓϥΠϚϦϊʔυ ͓લɺڞ༗ϝϞϦʹॻ͘ Զɺڞ༗ϝϞϦΛಡΉ ॻ͍ͨ Wayland Wayland ΫϥΠΞϯτ
  6. ϏσΦϝϞϦ Χʔωϧۭؒ Ϣʔβۭؒ Linux Mesa ϋʔυ΢ΣΞඇґଘͷ GPUͷૢ࡞ ಛఆͷGPU޲͚ͷ ίϚϯυ Ϩϯμʔϊʔυ

    libdrm QtɺGTK+౳ ΞϓϦέʔγϣϯ ΢Οδοτͷૢ࡞ ڞ༗ϝϞϦ ϓϥΠϚϦϊʔυ
  7. ϏσΦϝϞϦ Χʔωϧۭؒ Ϣʔβۭؒ Linux Mesa Ϩϯμʔϊʔυ libdrm QtɺGTK+౳ ΞϓϦέʔγϣϯ Ϣʔβۭؒ

    αʔό xcb ڞ༗ϝϞϦ͍ͩ͘͞ ڞ༗ϝϞϦ ͜ΕΛ࢖ͬͯ ͜͜ʹඳ͍ͯ ڞ༗ϝϞϦ ͍ͩ͘͞ Mesa libdrm ϓϥΠϚϦϊʔυ ͜ΕΛ ࢖ͬͯ
  8. ϏσΦϝϞϦ Χʔωϧۭؒ Ϣʔβۭؒ Linux Mesa Ϩϯμʔϊʔυ libdrm QtɺGTK+౳ ΞϓϦέʔγϣϯ Ϣʔβۭؒ

    αʔό xcb ඳ͖ऴΘͬͨ Mesa libdrm ϓϥΠϚϦϊʔυ ͔͜͜Β ͜͜΁ ίϐʔ ඳ͖ऴΘͬͨ ϋʔυ΢ΣΞඇґଘͷ GPUͷૢ࡞ ಛఆͷGPU޲͚ͷ ίϚϯυ
  9. ϏσΦϝϞϦ Χʔωϧۭؒ Ϣʔβۭؒ Linux Mesa Ϩϯμʔϊʔυ libdrm QtɺGTK+౳ ΞϓϦέʔγϣϯ Ϣʔβۭؒ

    αʔό xcb Mesa libdrm ϓϥΠϚϦϊʔυ ͜ΕΛදࣔͯ͠ ϋʔυ΢ΣΞඇґଘͷ GPUͷૢ࡞ ಛఆͷGPU޲͚ͷ ίϚϯυ
  10. ϏσΦϝϞϦ Χʔωϧۭؒ Ϣʔβۭؒ Linux Mesa Ϩϯμʔϊʔυ libdrm ΞϓϦέʔγϣϯ Ϣʔβۭؒ αʔό

    xcb ڞ༗ϝϞϦ Mesa libdrm ϓϥΠϚϦϊʔυ ϋʔυ΢ΣΞඇґଘͷ GPUͷૢ࡞ ಛఆͷGPU޲͚ͷ ίϚϯυ ΞϓϦέʔγϣϯ͕ Vulkan΍OpenGLΛ ௚઀஻Δ৔߹
  11. Ϣʔβۭؒ Mesa ΞϓϦέʔγϣϯ ϋʔυ΢ΣΞඇґଘͷ GPUͷૢ࡞ ಛఆͷGPU޲͚ͷ ίϚϯυ ϏσΦϝϞϦ Χʔωϧۭؒ Linux

    Ϩϯμʔϊʔυ ϓϥΠϚϦϊʔυ libdrm ඳ͍ͯ ΋͠ը໘ʹද͕ࣔͨ͠Δϓϩηε͕ ϗετʹ1͔ͭ͠ͳ͍ͳΒ
  12. ϏσΦϝϞϦ Χʔωϧۭؒ Ϣʔβۭؒ Linux Mesa Ϩϯμʔϊʔυ ΞϓϦέʔγϣϯ ϓϥΠϚϦϊʔυ ΋͠ը໘ʹද͕ࣔͨ͠Δϓϩηε͕ ϗετʹ1͔ͭ͠ͳ͍ͳΒ

    Q. ͜Ε͕Ͱ͖ΔഺͰ͸ libdrm ͜ΕΛදࣔͯ͠ ϋʔυ΢ΣΞඇґଘͷ GPUͷૢ࡞ ಛఆͷGPU޲͚ͷ ίϚϯυ
  13. Instance Extensions: count = 20 =============================== VK_EXT_acquire_drm_display : extension revision

    1 VK_EXT_acquire_xlib_display : extension revision 1 VK_EXT_debug_report : extension revision 10 VK_EXT_debug_utils : extension revision 2 VK_EXT_direct_mode_display : extension revision 1 VK_EXT_display_surface_counter : extension revision 1 VK_KHR_device_group_creation : extension revision 1 VK_KHR_display : extension revision 23 VK_KHR_external_fence_capabilities : extension revision 1 VK_KHR_external_memory_capabilities : extension revision 1 VK_KHR_external_semaphore_capabilities : extension revision 1 VK_KHR_get_display_properties2 : extension revision 1 VK_KHR_get_physical_device_properties2 : extension revision 2 VK_KHR_get_surface_capabilities2 : extension revision 1 VK_KHR_portability_enumeration : extension revision 1 VK_KHR_surface : extension revision 25 VK_KHR_surface_protected_capabilities : extension revision 1 VK_KHR_wayland_surface : extension revision 6 VK_KHR_xcb_surface : extension revision 6 ͜Ε
  14. #include <iostream> #include <cstdint> #include <vector> #include <vulkan/vulkan.hpp> #include <nlohmann/json.hpp>

    int main( int argc, const char *argv[] ) { #ifdef VULKAN_HPP_DISPATCH_LOADER_DYNAMIC // Vulkan-Hpp͔ΒvkCreateInstanceΛݺ΂ΔΑ͏ʹ͢Δ vk::DynamicLoader dl; PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = dl.getProcAddress<PFN_vkGetInstanceProcAddr>( "vkGetInstanceProcAddr" ); VULKAN_HPP_DEFAULT_DISPATCHER.init( vkGetInstanceProcAddr ); #endif const auto app_info = vk::ApplicationInfo() // ΞϓϦέʔγϣϯͷ໊લ .setPApplicationName( argc ? argv[ 0 ] : "my_application" ) // ΞϓϦέʔγϣϯͷόʔδϣϯ .setApplicationVersion( VK_MAKE_VERSION(1, 0, 0) ) // Τϯδϯͷ໊લ
  15. const std::vector< const char * > layers{ "VK_LAYER_KHRONOS_validation" }; //

    ҎԼͷ֦ுΛ࢖͏ const std::vector< const char* > exts{ "VK_KHR_surface", // දࣔ͢ΔҝͷඳըΛߦ͏ "VK_KHR_display" // σΟεϓϨΠͷૢ࡞Λߦ͏ }; // ΠϯελϯεΛ࡞੒ auto instance = vk::createInstanceUnique( vk::InstanceCreateInfo() // ΞϓϦέʔγϣϯͷ৘ใΛࢦఆ .setPApplicationInfo( &app_info ) // ࢖༻͢ΔϨΠϠʔΛࢦఆ .setPEnabledLayerNames( layers ) // ࢖༻͢Δ֦ுΛࢦఆ .setPEnabledExtensionNames( exts ) );
  16. const auto devices = instance->enumeratePhysicalDevices(); for( const auto &device: devices

    ) { // GPUͷ৘ใΛऔಘ const auto props = device.getProperties(); // ͜ͷGPUʹܨ͕͍ͬͯΔσΟεϓϨΠͷ৘ใΛऔಘ const auto displays = device.getDisplayPropertiesKHR(); for( const auto &display: displays ) { std::cout << "GPU " << props.deviceName << " ʹσΟεϓϨΠ " << display.displayName << " ͕઀ଓ͞Ε͍ͯΔ" << std::endl; std::cout << " ࠷େղ૾౓ : " << display.physicalResolution.width << "x" << display.physicalResolution.height << std::endl; // σΟεϓϨΠͷରԠϞʔυΛऔಘ const auto modes = device.getDisplayModePropertiesKHR( display.display ); for( const auto &mode: modes ) { std::cout << " ରԠϞʔυ : " << GPUͱσΟεϓϨΠͷ৘ใΛऔಘ
  17. for( const auto &display: displays ) { std::cout << "GPU

    " << props.deviceName << " ʹσΟεϓϨΠ " << display.displayName << " ͕઀ଓ͞Ε͍ͯΔ" << std::endl; std::cout << " ࠷େղ૾౓ : " << display.physicalResolution.width << "x" << display.physicalResolution.height << std::endl; // σΟεϓϨΠͷରԠϞʔυΛऔಘ const auto modes = device.getDisplayModePropertiesKHR( display.display ); for( const auto &mode: modes ) { std::cout << " ରԠϞʔυ : " << mode.parameters.visibleRegion.width << "x" << mode.parameters.visibleRegion.height << "@" << float( mode.parameters.refreshRate ) / 1000.f << "Hz" << std::endl; } } } ར༻ՄೳͳදࣔϞʔυΛऔಘ
  18. $ ./src/example/list_displays/list_displays Vulkan 1.3.224 GPU NVIDIA GeForce RTX 2070 ʹσΟεϓϨΠ

    Asustek Computer Inc PA32UCX (DP-0) ͕઀ଓ͞Ε͍ͯΔ ࠷େղ૾౓ : 3840x2160 ରԠϞʔυ : [email protected] ରԠϞʔυ : 3840x2160@50Hz ରԠϞʔυ : [email protected] ରԠϞʔυ : 3840x2160@25Hz ରԠϞʔυ : [email protected] ରԠϞʔυ : [email protected] ରԠϞʔυ : [email protected]
  19. // Πϯελϯε͕αϙʔτ͢ΔVulkanͷόʔδϣϯΛऔಘ const auto version = vk::enumerateInstanceVersion(); std::cout << "Vulkan

    " << VK_VERSION_MAJOR( version ) << "." << VK_VERSION_MINOR( version ) << "." << VK_VERSION_PATCH( version ) << std::endl; // 1ݸ໨ͷGPUʹܨ͕͍ͬͯΔ1ݸ໨ͷσΟεϓϨΠΛ1ݸ໨ͷදࣔϞʔυͰ࢖͏ const auto devices = instance->enumeratePhysicalDevices(); if( devices.empty() ) std::abort(); const auto displays = devices[ 0 ].getDisplayPropertiesKHR(); if( displays.empty() ) std::abort(); const auto modes = devices[ 0 ].getDisplayModePropertiesKHR( displays[ 0 ].display ); if( modes.empty() ) std::abort(); // ͜ͷ৚݅ͰαʔϑΣεΛ࡞Δ const auto surface = instance->createDisplayPlaneSurfaceKHRUnique( vk::DisplaySurfaceCreateInfoKHR() .setDisplayMode( modes[ 0 ].displayMode ) .setImageExtent( modes[ 0 ].parameters.visibleRegion )
  20. if( modes.empty() ) std::abort(); // ͜ͷ৚݅ͰαʔϑΣεΛ࡞Δ const auto surface =

    instance->createDisplayPlaneSurfaceKHRUnique( vk::DisplaySurfaceCreateInfoKHR() .setDisplayMode( modes[ 0 ].displayMode ) .setImageExtent( modes[ 0 ].parameters.visibleRegion ) ); // αʔϑΣεͰར༻ՄೳͳϐΫηϧϑΥʔϚοτΛऔಘ͢Δ const auto available_formats = devices[ 0 ].getSurfaceFormatsKHR( *surface ); for( const auto &format: available_formats ) { std::cout << "ར༻ՄೳͳϑΥʔϚοτ : " << nlohmann::json( format ) << std::endl; } // ཉ͍͠ϐΫηϧύοΩϯάํ๏ const std::vector< vk::Format > expected_formats{ vk::Format::eA2B10G10R10UnormPack32, // 30bit BGR vk::Format::eA8B8G8R8UnormPack32, // 24bit BGR
  21. const std::vector< vk::Format > expected_formats{ vk::Format::eA2B10G10R10UnormPack32, // 30bit BGR vk::Format::eA8B8G8R8UnormPack32,

    // 24bit BGR vk::Format::eB8G8R8A8Unorm, // 24bit RGB vk::Format::eR5G6B5UnormPack16 // 16bit RGB }; // ৭ۭ͕ؒsRGBͰ্هͷํ๏ͰύοΩϯά͞ΕͨϑΥʔϚοτΛ୳͢ vk::Format selected_format = vk::Format::eUndefined; for( const auto &e: expected_formats ) { if( std::find_if( available_formats.begin(), available_formats.end(), [e]( const auto &f ) { return f.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear && f.format == e; } ) != available_formats.end() ) { selected_format = e; break; } }
  22. // αʔϑΣεͷ৘ใΛऔಘ const auto surface_capabilities = devices[ 0 ].getSurfaceCapabilitiesKHR( *surface

    ); std::cout << "αʔϑΣεͷ࠷খΠϝʔδ਺ : " << surface_capabilities.minImageCount << std::endl; std::cout << "αʔϑΣεͷ࠷େΠϝʔδ਺ : " << surface_capabilities.maxImageCount << std::endl; // σόΠεʹඋΘ͍ͬͯΔΩϡʔΛऔಘ const auto queue_props = devices[ 0 ].getQueueFamilyProperties(); uint32_t queue_family_index = 0u; // ඳըཁٻΛड͚෇͚ΔΩϡʔΛ୳͢ for( uint32_t i = 0; i < queue_props.size(); ++i ) { if( queue_props[ i ].queueFlags & vk::QueueFlagBits::eGraphics ) { queue_family_index = i; break; } } αʔϑΣε্Ͱར༻ՄೳͳΠϝʔδͷ਺Λऔಘ
  23. ϏσΦϝϞϦ Χʔωϧۭؒ Ϣʔβۭؒ Linux Mesa ΞϓϦέʔγϣϯ දࣔ ϋʔυ΢ΣΞඇґଘͷ GPUͷૢ࡞ ಛఆͷGPU޲͚ͷ

    ίϚϯυ ඳը ϓϥΠϚϦϊʔυ ੾Γସ͑ දࣔத libdrm ඳ͖͔͚͕ දࣔ͞Εͯ͸͍͚ͳ͍ αʔϑΣε͸ 2ը໘෼Ҏ্ͷαΠζͰ ֬อ͞ΕΔ ඳ͖ऴΘͬͨΒ දࣔ͢ΔΠϝʔδΛ ੾Γସ͑Δ
  24. // σόΠεʹඋΘ͍ͬͯΔΩϡʔΛऔಘ const auto queue_props = devices[ 0 ].getQueueFamilyProperties(); uint32_t

    queue_family_index = 0u; // ඳըཁٻΛड͚෇͚ΔΩϡʔΛ୳͢ for( uint32_t i = 0; i < queue_props.size(); ++i ) { if( queue_props[ i ].queueFlags & vk::QueueFlagBits::eGraphics ) { queue_family_index = i; break; } } // ҎԼͷσόΠε֦ுΛ࢖͏ std::vector< const char* > dexts{ "VK_KHR_swapchain" // εϫοϓνΣΠϯΛ࢖͏ }; const float priority = 0.0f; // ඳըཁٻΛड͚෇͚ΔΩϡʔΛ1͍ͭͩ͘͞ std::vector< vk::DeviceQueueCreateInfo > queues{ ෺ཧσόΠε શछରԠ શछରԠ ܭࢉઐ༻ ܭࢉઐ༻ సૹઐ༻ Ωϡʔ ͜͜ʹίϚϯυΛྲྀ͢ͱ GPUͰίϚϯυ͕ ࣮ߦ͞ΕΔ
  25. // σόΠεʹඋΘ͍ͬͯΔΩϡʔΛऔಘ const auto queue_props = devices[ 0 ].getQueueFamilyProperties(); uint32_t

    queue_family_index = 0u; // ඳըཁٻΛड͚෇͚ΔΩϡʔΛ୳͢ for( uint32_t i = 0; i < queue_props.size(); ++i ) { if( queue_props[ i ].queueFlags & vk::QueueFlagBits::eGraphics ) { queue_family_index = i; break; } } // ҎԼͷσόΠε֦ுΛ࢖͏ std::vector< const char* > dexts{ "VK_KHR_swapchain" // εϫοϓνΣΠϯΛ࢖͏ }; const float priority = 0.0f; // ඳըཁٻΛड͚෇͚ΔΩϡʔΛ1͍ͭͩ͘͞ std::vector< vk::DeviceQueueCreateInfo > queues{ ෺ཧσόΠε GPUͷৼΔ෣͍ͷઃఆ ࿦ཧσόΠε ࢖͏෺ཧσόΠε ࢖͏Ωϡʔ GPUͷઃఆ ࿦ཧσόΠε + + =
  26. const float priority = 0.0f; // ඳըཁٻΛड͚෇͚ΔΩϡʔΛ1͍ͭͩ͘͞ std::vector< vk::DeviceQueueCreateInfo >

    queues{ vk::DeviceQueueCreateInfo() .setQueueFamilyIndex( queue_family_index ) .setQueuePriorities( priority ) }; // ࿦ཧσόΠεΛ࡞Δ auto device = devices[ 0 ].createDeviceUnique( vk::DeviceCreateInfo() // ͜ͷΩϡʔΛ࢖͏ .setQueueCreateInfos( queues ) // ͜ͷσόΠε֦ுΛ࢖͏ .setPEnabledExtensionNames( dexts ) ); // σόΠε͔ΒΩϡʔΛऔಘ auto queue = device->getQueue( queue_family_index, 0u );
  27. // εϫοϓνΣʔϯΛ࡞Δ const auto swapchain = device->createSwapchainKHRUnique( vk::SwapchainCreateInfoKHR() // ͜ͷαʔϑΣεʹඳը݁ՌΛૹΔ

    .setSurface( *surface ) // εϫοϓνΣʔϯͷΠϝʔδͷ਺ .setMinImageCount( surface_capabilities.minImageCount ) // εϫοϓνΣʔϯͷΠϝʔδͷϑΥʔϚοτ .setImageFormat( selected_format ) // εϫοϓνΣʔϯͷΠϝʔδͷ৭ۭؒ .setImageColorSpace( vk::ColorSpaceKHR::eSrgbNonlinear ) // εϫοϓνΣʔϯͷΠϝʔδͷେ͖͞ .setImageExtent( surface_capabilities.currentExtent ) // ϨΠϠʔ͸1ͭ .setImageArrayLayers( 1u ) // εϫοϓνΣʔϯͷΠϝʔδ͸సૹઌʹ΋࢖͑Δඞཁ͕͋Δ .setImageUsage( vk::ImageUsageFlagBits::eTransferDst αʔϑΣεͷͲͷΠϝʔδ͕දࣔத͔Λอ࣋͢Δ දࣔதͩͬͨΠϝʔδ͕දࣔதͰͳ͘ͳͬͨΒڭ͑ͯ͘ΕΔ εϫοϓνΣΠϯ
  28. // εϫοϓνΣʔϯͷΠϝʔδΛऔಘ͢Δ const auto swapchain_images = device- >getSwapchainImagesKHR( *swapchain );

    std::cout << "εϫοϓνΣΠϯͷΠϝʔδͷ਺ : " << swapchain_images.size() << std::endl; // ίϚϯυϓʔϧΛ࡞Δ const auto command_pool = device->createCommandPoolUnique( vk::CommandPoolCreateInfo() .setQueueFamilyIndex( 0u ) ); { // ίϚϯυόοϑΝΛ࡞Δ const auto command_buffers = device->allocateCommandBuffersUnique( vk::CommandBufferAllocateInfo() .setCommandPool( *command_pool ) .setLevel( vk::CommandBufferLevel::ePrimary ) .setCommandBufferCount( 1u ) εϫοϓνΣΠϯ͔ΒશͯͷΠϝʔδΛऔಘ͠
  29. // εϫοϓνΣΠϯͷΠϝʔδΛ੨ͰృΓͭͿ͢ command_buffer->clearColorImage( image, // ͜ͷΠϝʔδΛ vk::ImageLayout::eTransferDstOptimal, // ੨ vk::ClearColorValue()

    .setFloat32( std::array< float, 4u >{ 0.f, 0.f, 1.f, 1.f } ), { vk::ImageSubresourceRange() // ৭Λॻ͖׵͑Δ .setAspectMask( vk::ImageAspectFlagBits::eColor ) // 0൪໨ͷϛοϓϨϕϧ .setBaseMipLevel( 0u ) .setLevelCount( 1u ) // 0൪໨ͷϨΠϠʔ .setBaseArrayLayer( 0u ) .setLayerCount( 1u ) } ); ੨ͰృΓͭͿ͢ίϚϯυΛ༻ҙ
  30. .setAspectMask( vk::ImageAspectFlagBits::eColor ) .setBaseMipLevel( 0u ) .setLevelCount( 1u ) .setBaseArrayLayer(

    0u ) .setLayerCount( 1u ) ) } ); } // ίϚϯυόοϑΝͷه࿥ΛऴΘΔ command_buffer->end(); // ίϚϯυόοϑΝΛΩϡʔʹྲྀ͢ queue.submit( vk::SubmitInfo() .setCommandBufferCount( 1u ) .setPCommandBuffers( &*command_buffer ), VK_NULL_HANDLE ); // Ωϡʔʹྲྀͨ͠ίϚϯυ͕׬ྃ͢Δ·Ͱ଴ͭ queue.waitIdle(); } ը໘ʹදࣔΛ࢝ΊΔલʹ αʔϑΣεΛ੨ͰృΓͭͿ͓ͯ͘͠ ίϚϯυΛΩϡʔʹྲྀͯ͠׬ྃΛ଴ͭ
  31. uint32_t current_frame = 0u; while( 1 ) { const auto

    begin_time = std::chrono::high_resolution_clock::now(); // εϫοϓνΣΠϯ͔Β࣍ʹॻ͘΂͖ΠϝʔδͷindexΛ໯͏ std::uint32_t image_index = device->acquireNextImageKHR( // ͜ͷεϫοϓνΣʔϯ͔Β *swapchain, // Πϝʔδ͕໯͑Δ·Ͱ͍͘ΒͰ΋଴ͭ std::numeric_limits< std::uint64_t >::max(), // Πϝʔδ͕ද͔ࣔΒ֎ΕͨΒ͜ͷηϚϑΥʹ௨஌ *image_acquired[ current_frame ], VK_NULL_HANDLE ).value; // ΠϝʔδΛॻ͖׵͑ͳ͍Ͱ͙͢ʹදࣔʹ·Θ͢ if( queue.presentKHR( vk::PresentInfoKHR() εϫοϓνΣΠϯ͔Β ඳ͍ͯ΋େৎ෉ͳαʔϑΣεͷΠϝʔδͷΠϯσοΫεΛ໯͍
  32. VK_NULL_HANDLE ).value; // ΠϝʔδΛॻ͖׵͑ͳ͍Ͱ͙͢ʹදࣔʹ·Θ͢ if( queue.presentKHR( vk::PresentInfoKHR() .setWaitSemaphoreCount( 1u )

    // ͜ͷηϑΥʹ௨஌͕དྷΔ·Ͱ଴͔ͬͯΒ .setPWaitSemaphores( &*image_acquired[ current_frame ] ) .setSwapchainCount( 1 ) // ͜ͷεϫοϓνΣʔϯͷ .setPSwapchains( &*swapchain ) // ͜ͷΠϝʔδΛαʔϑΣεʹૹΔ .setPImageIndices( &image_index ) ) != vk::Result::eSuccess ) std::abort(); ++current_frame; current_frame %= swapchain_images.size(); // 1ϑϨʔϜͷද͕ࣔ࣌ؒܦա͢Δ·Ͱ଴ͭ ಺༰Λॻ͖׵͑ͳ͍Ͱ͙͢ʹදࣔʹ·Θ͢
  33. // ͜ͷηϑΥʹ௨஌͕དྷΔ·Ͱ଴͔ͬͯΒ .setPWaitSemaphores( &*image_acquired[ current_frame ] ) .setSwapchainCount( 1 )

    // ͜ͷεϫοϓνΣʔϯͷ .setPSwapchains( &*swapchain ) // ͜ͷΠϝʔδΛαʔϑΣεʹૹΔ .setPImageIndices( &image_index ) ) != vk::Result::eSuccess ) std::abort(); ++current_frame; current_frame %= swapchain_images.size(); // 1ϑϨʔϜͷද͕ࣔ࣌ؒܦա͢Δ·Ͱ଴ͭ const auto end_time = std::chrono::high_resolution_clock::now(); const auto elapsed_time = end_time - begin_time; if( elapsed_time < frame_time ) { const auto sleep_for = frame_time - elapsed_time; std::this_thread::sleep_for( sleep_for ); } } ͜ΕΛσΟεϓϨΠͷϦϑϨογϡϨʔτʹಉظͯ͠܁Γฦ͢
  34. struct rect_info { vec4 color; u16vec2 offset; u16vec2 extent; u16vec2

    semantic; uint16_t texid; uint16_t depth; }; layout(std430,binding = 0) buffer Uniforms { mat4 world_matrix; rect_info rects[ 65536 ]; float scale_factor; } uniforms; CPU͔Βॻ͍ͯGPUͰಡΉ৘ใͷߏ଄ମ ϐΫηϧ࠲ඪܥ΁ͷม׵ߦྻ (−1, − 1) (−1,1) (1,1) (1, − 1) VulkanͷεΫϦʔϯ࠲ඪܥ ϐΫηϧ࠲ඪܥ (0,2040) (0,0) (3840,0) (3840,2040)
  35. struct rect_info { vec4 color; u16vec2 offset; u16vec2 extent; u16vec2

    semantic; uint16_t texid; uint16_t depth; }; layout(std430,binding = 0) buffer Uniforms { mat4 world_matrix; rect_info rects[ 65536 ]; float scale_factor; } uniforms; CPU͔Βॻ͍ͯGPUͰಡΉ৘ใͷߏ଄ମ ௕ํܗͷ৘ใͷ഑ྻ ͜ͷ৭Ͱ ͜ͷϐΫηϧ͔Β ͜ͷαΠζͷ ௕ํܗΛඳ͍ͯ (0,2040) (0,0) (3840,0) (3840,2040)
  36. struct rect_info { vec4 color; u16vec2 offset; u16vec2 extent; u16vec2

    semantic; uint16_t texid; uint16_t depth; }; layout(std430,binding = 0) buffer Uniforms { mat4 world_matrix; rect_info rects[ 65536 ]; float scale_factor; } uniforms; Ұ౓ͷDrawͰ࠷େ65536ݸͷ Λඳ͘ ௕ํܗ ͷ਺͕65536ݸΛ௒͑Δ৔߹͸ෳ਺ͷίϚϯυʹ෼͚Δ ௕ํܗ
  37. void main() { vec4 pixel_pos = vec4( ( input_position.x *

    float( uint( uniforms.rects[ gl_InstanceIndex ].extent.x ) ) + float( uint( uniforms.rects[ gl_InstanceIndex ].offset.x ) ) ) * uniforms.scale_factor, ( input_position.y * float( uint( uniforms.rects[ gl_InstanceIndex ].extent.y ) ) + float( uint( uniforms.rects[ gl_InstanceIndex ].offset.y ) ) ) * uniforms.scale_factor, float( 65535 - uint( uniforms.rects[ gl_InstanceIndex ].depth ) ) / 65536.f, 1.0 ); gl_Position = uniforms.world_matrix * pixel_pos; output_color = vec4( uniforms.rects[ gl_InstanceIndex ].color.r, uniforms.rects[ gl_InstanceIndex ].color.g, uniforms.rects[ gl_InstanceIndex ].color.b, 1.0 ); Πϯελϯεຖʹ͜ͷ஋͕มΘΔ (0,1) (0,0) (1,0) (1,1) ΛඞཁͳαΠζʹͯ͠ εΫϦʔϯ࠲ඪܥʹม׵ͯ͠ϥελϥΠβ΁
  38. void rect_renderer::add_rectangle_internal( const vk::Rect2D &rect, const std::array< float, 4u >

    &color, std::uint16_t depth, std::uint16_t semantic_id, std::uint16_t texid ) { const auto instance_id = instance_count++; uniforms_host.rects[ instance_id ].offset[ 0 ] = rect.offset.x * scale; uniforms_host.rects[ instance_id ].offset[ 1 ] = rect.offset.y * scale; uniforms_host.rects[ instance_id ].extent[ 0 ] = rect.extent.width * scale; uniforms_host.rects[ instance_id ].extent[ 1 ] = rect.extent.height * scale; uniforms_host.rects[ instance_id ].color = glm::vec4( color[ 0 ], color[ 1 ], color[ 2 ], color[ 3 ] ); uniforms_host.rects[ instance_id ].depth = depth; uniforms_host.rects[ instance_id ].texid = texid; uniforms_host.rects[ instance_id ].semantic[ 0 ] = 0; uniforms_host.rects[ instance_id ].semantic[ 1 ] = semantic_id; } ௕ํܗͷ௥ՃΛཁٻ͞ΕͨΒ GPU͔ΒಡΉߏ଄ମʹ৘ใΛੵΜͰ͓͘
  39. std::random_device seedgen; std::mt19937 rng( seedgen() ); std::uniform_int_distribution<> xdist( 0u, 3839u

    ); std::uniform_int_distribution<> ydist( 0u, 2159u ); std::uniform_int_distribution<> cdist( 0u, 0xFFFFFFu ); std::uniform_int_distribution<> ddist( 0u, 32767u ); for( std::size_t i = 0u; i != 1024u; ++i ) { auto x0 = xdist( rng ); auto x1 = xdist( rng ); if( x0 > x1 ) std::swap( x0, x1 ); auto y0 = ydist( rng ); auto y1 = ydist( rng ); if( y0 > y1 ) std::swap( y0, y1 ); auto c = cdist( rng ); auto d = ddist( rng ); window.add_rectangle( vk::Rect2D{ vk::Offset2D{ x0, y0 }, vk::Extent2D{ x1 - x0, y1 - y0 } }, gct::srgb_oetf( gct::html_color( c ) ), d, true, 0u, 0u, 0u ); } ཚ਺ͰܾΊͨҐஔʹཚ਺ͰܾΊͨ৭ͷ Λඳ͘ ௕ํܗ
  40. void rect_renderer::draw( gct::command_buffer_recorder_t &recorder ) { recorder.bind_pipeline( pipeline ); recorder.bind_descriptor_set(

    vk::PipelineBindPoint::eGraphics, pipeline_layout, descriptor_set ); recorder.bind_vertex_buffer( shape.vertex_buffer ); recorder->draw( shape.vertex_count, instance_count, 0, 0 ); } ͜ͷඳ͖ํͰ ͔͜͜Β ߏ଄ମͷσʔλΛरͬͯ (0,1) (0,0) (1,0) (1,1) Λ 6௖఺ 1024ݸ
  41. flat layout (location = 0) in vec4 input_color; flat layout

    (location = 1) in uvec2 input_semantic; flat layout (location = 2) in uint input_texid; layout (location = 3) in vec2 input_texcoord; layout (location = 0) out vec4 output_color; layout (location = 1) out vec2 output_semantic; layout(binding = 1) uniform sampler2D tex[ 16 ]; void main() { if( input_texid == 0 ) { if( input_color.a == 0.0 ) { discard; } output_color = input_color; } else { vec4 sampled = texture( tex[ input_texid - 1 ], input_texcoord ); if( sampled.a == 0.0 ) { discard; } output_color = sampled; } output_semantic = input_semantic; } input_texid ͕0Ͱͳ͚Ε͹ -1൪໨ͷςΫενϟ͔Β৭ΛऔΔ input_texid
  42. for( const auto &image: input_images ) { updates.push_back( gct::write_descriptor_set_t() .set_basic(

    (*descriptor_set)[ "tex" ] .setDstArrayElement( tex_id ) .setDescriptorCount( 1 ) ) .add_image( gct::descriptor_image_info_t() .set_sampler( sampler ) .set_image_view( image ) .set_basic( vk::DescriptorImageInfo() .setImageLayout( vk::ImageLayout::eShaderReadOnlyOptimal ) ) ) ); ++tex_id; } canvasʹηοτ͞ΕͨΠϝʔδΛ ςΫενϟαϯϓϥʔʹ݁ͼ͚ͭΔ
  43. gct::canvas window( device, allocator, pipeline_cache, descriptor_pool, wvs, wfs, vk::Extent2D{ width,

    height }, std::array< float, 4u >{ 0.f, 0.f, 0.f, 0.f }, {}, { image->get_view( gct::image_view_create_info_t() .set_basic( vk::ImageViewCreateInfo() .setSubresourceRange( vk::ImageSubresourceRange() .setAspectMask( vk::ImageAspectFlagBits::eColor ) .setBaseMipLevel( 0 ) .setLevelCount( image->get_props().get_basic().mipLevels ) .setBaseArrayLayer( 0 ) .setLayerCount( image->get_props().get_basic().arrayLayers ) ) .setViewType( gct::to_image_view_type( image->get_props().get_basic().imageType ) ) .setFormat( vk::Format::eR8G8B8A8Srgb ) ) .rebuild_chain() ) } ); canvasͷҾ਺ʹΠϝʔδΛ౉͢
  44. gct::canvas window( device, allocator, pipeline_cache, descriptor_pool, wvs, wfs, vk::Extent2D{ 640u,

    300u }, std::array< float, 4u >{ 0.f, 0.f, 0.f, 0.f }, {}, { icon->get_view( gct::image_view_create_info_t() .set_basic( vk::ImageViewCreateInfo() .setSubresourceRange( vk::ImageSubresourceRange() .setAspectMask( vk::ImageAspectFlagBits::eColor ) .setBaseMipLevel( 0 ) .setLevelCount( icon->get_props().get_basic().mipLevels ) .setBaseArrayLayer( 0 ) 640x300ͷ΢Οϯυ΢Λ࡞ͬͯ
  45. { icon->get_view( gct::image_view_create_info_t() .set_basic( vk::ImageViewCreateInfo() .setSubresourceRange( vk::ImageSubresourceRange() .setAspectMask( vk::ImageAspectFlagBits::eColor )

    .setBaseMipLevel( 0 ) .setLevelCount( icon->get_props().get_basic().mipLevels ) .setBaseArrayLayer( 0 ) .setLayerCount( icon->get_props().get_basic().arrayLayers ) ) .setViewType( gct::to_image_view_type( icon->get_props().get_basic().imageType ) ) .setFormat( vk::Format::eR8G8B8A8Srgb ) ) .rebuild_chain() ) } ); ࢖͏ը૾Ληοτͯ͠
  46. window.add_rectangle( vk::Rect2D{ vk::Offset2D{ 0, 0 }, vk::Extent2D{ 640, 300 }

    }, gct::srgb_oetf( gct::html_color( 0xf0f8ff ) ), 0u, true, 0u, 0u, 0u ); window.add_rectangle( vk::Rect2D{ vk::Offset2D{ 95, 50 }, vk::Extent2D{ 200, 70 } }, gct::srgb_oetf( gct::html_color( 0xc0c0c0 ) ), 1u, true, 0u, 0u, 1u ); window.add_rectangle( vk::Rect2D{ vk::Offset2D{ 345, 50 }, vk::Extent2D{ 200, 70 } }, gct::srgb_oetf( gct::html_color( 0x4169e1 ) ), 1u, true, 0u, 0u, 2u ); window.add_rectangle( vk::Rect2D{ vk::Offset2D{ 0, 0 }, vk::Extent2D{ 640, 300 } }, gct::srgb_oetf( gct::html_color( 0x000000 ) ), 1u, false, 1u, 0u, 0u ); window.add_rectangle( vk::Rect2D{ vk::Offset2D{ 50, 150 }, vk::Extent2D{ 128, 128 } }, Ϙλϯͱ͔ฒ΂ͯ
  47. desktop.push_back( std::shared_ptr< gct::canvas >( new gct::canvas( device, allocator, pipeline_cache, descriptor_pool,

    wvs, dfs, vk::Extent2D{ width, height }, gct::srgb_oetf( gct::html_color( 0x008080 ) ), {}, { window.get_color()->get_view( vk::ImageAspectFlagBits::eColor ), } ) ) ); desktop.back()->add_rectangle( vk::Rect2D{ vk::Offset2D{ ( width - 640u ) / 2u, ( height - 300u ) / 2u }, vk::Extent2D{ 640, 300 } }, gct::srgb_oetf( gct::html_color( 0x000000 ) ), 1u, true, 0u, 1u, 0u ); canvasʹ͸ผͷcanvasͷඳը݁ՌΛ ೖྗͱͯ͠౉ͤΔ
  48. PathFinder https://github.com/servo/pathfinder ίϯϐϡʔτγΣʔμͰϥελϥΠζ NanoVG libtess2 https://github.com/memononen/libtess2 CPUͰࡾ֯ܗ෼ׂɺGPUͰඳը https://github.com/memononen/nanovg CPUͰߥ͘ࡾ֯ܗ෼ׂɺGPUͰඳըɺεςϯγϧͰमਖ਼ Cairo

    https://www.cairographics.org/ CPUͰϥελϥΠζ CPUͰ΍Δ GPUͰ΍Δ GPUͰૉૣ͘ϕΫλը૾Λඳ͘ͷ͸؆୯Ͱ͸ͳ͍ OpenGL༻ OpenGL༻ OpenGL༻ ͔͠΋طଘͷ࣮૷͸େମVulkanͳΞϓϦέʔγϣϯʹ૊ΈࠐΊͳ͍
  49. Φϑηοτςʔϒϧ ςʔϒϧ ςʔϒϧ ςʔϒϧ ⋮ 55'ͷόʔδϣϯ ςʔϒϧ਺ ςʔϒϧͷछྨ νΣοΫαϜ ςʔϒϧͷઌ಄ͷҐஔ

    ςʔϒϧͷ௕͞ ςʔϒϧͷछྨ νΣοΫαϜ ςʔϒϧͷઌ಄ͷҐஔ ςʔϒϧͷ௕͞ ⋮ TrueTypeͷߏ଄
  50. TrueTypeͷߏ଄ ςʔϒϧ໊ ಺༰ head name OS/2 maxp post ϝλσʔλ glyf

    άϦϑΛߏ੒͢Δۂઢͷύϥϝʔλ loca Կ൪໨ͷάϦϑ͕glyfͷ Կཁૉ໨͔Β࢝·Δ͔ cmap locaͷԿ൪໨ͷάϦϑ͕ จࣈίʔυͰݴ͏ԿͷจࣈʹରԠ͢Δ͔
  51. TrueTypeͷߏ଄ - cmap DNBQͷܗࣜ ಺༰ Format 0 256ཁૉͷ഑ྻͰ8bitจࣈΛม׵ Format 2

    2όΠτจࣈΛ্Ґ8bitͱԼҐ8bitʹ۠੾ͬͯ2ஈ֊Ͱม׵ Format 4 16bitͷ೚ҙͷൣғͱάϦϑͷ೚ҙͷൣғͷରԠΛ࣋ͭ UnicodeͷBMPͷൣғ͚ͩΛѻ͑Δ Format 6 ࠷େ65536ཁૉͷ഑ྻͰ16bitจࣈΛม׵ Format 8 Unicodeͷ্Ґ16bitͱԼҐ16bitͰม׵දΛ෼͚ͯ2ஈ֊Ͱม׵͢Δ Format 10 ࠷େ4294967296ཁૉͷ഑ྻͰ32bitจࣈΛม׵ Format 12 32bitͷ೚ҙͷൣғͱάϦϑͷ೚ҙͷൣғͷରԠΛ࣋ͭ UnicodeͷશͯͷจࣈΛѻ͑Δ Format 13 32bitͷ೚ҙͷൣғͱಛఆͷάϦϑͷରԠΛ࣋ͭ UnicodeͷશͯͷจࣈΛѻ͑Δ Format 14 UnicodeͷҟࣈମηϨΫλͰબ͹ΕΔจࣈͱάϦϑͷରԠΛ࣋ͭ
  52. TrueTypeͷߏ଄ - cmap DNBQͷܗࣜ ಺༰ Format 0 256ཁૉͷ഑ྻͰ8bitจࣈΛม׵ Format 2

    2όΠτจࣈΛ্Ґ8bitͱԼҐ8bitʹ۠੾ͬͯ2ஈ֊Ͱม׵ Format 4 16bitͷ೚ҙͷൣғͱάϦϑͷ೚ҙͷൣғͷରԠΛ࣋ͭ UnicodeͷBMPͷൣғ͚ͩΛѻ͑Δ Format 6 ࠷େ65536ཁૉͷ഑ྻͰ16bitจࣈΛม׵ Format 8 Unicodeͷ্Ґ16bitͱԼҐ16bitͰม׵දΛ෼͚ͯ2ஈ֊Ͱม׵͢Δ Format 10 ࠷େ4294967296ཁૉͷ഑ྻͰ32bitจࣈΛม׵ Format 12 32bitͷ೚ҙͷൣғͱάϦϑͷ೚ҙͷൣғͷରԠΛ࣋ͭ UnicodeͷશͯͷจࣈΛѻ͑Δ Format 13 32bitͷ೚ҙͷൣғͱಛఆͷάϦϑͷରԠΛ࣋ͭ UnicodeͷશͯͷจࣈΛѻ͑Δ Format 14 UnicodeͷҟࣈମηϨΫλͰબ͹ΕΔจࣈͱάϦϑͷରԠΛ࣋ͭ 16bitͷUnicode͔͠ѻ͑ͳ͍ݹ͍ιϑτ΢ΣΞͰ΋ϑΥϯτΛ࢖͑ΔΑ͏ʹ 
 ͜ͷܗࣜͷcmap΋Α͘ಉࠝ͞Ε͍ͯΔ ΠϚυΩͷϑΥϯτ͸େମ͜ͷܗࣜͷcmapΛؚΜͰ͍Δ
  53. Unicodeͷۭؒ جຊଟݴޠ໘ #.1 ௥Ճଟݴޠ໘ 4.1 ௥Ճ׽ࣈ໘ 4*1 ୈࡾ׽ࣈ໘ 5*1 ະׂΓ౰ͯ

    ௥Ճಛघ༻్໘ 441 ࢲ༻໘ 0x000000 0x010000 0x020000 0x030000 0x040000 0x0F0000 0x110000 0x0E0000 16bitͰදݱͰ͖Δൣғ ೔ຊޠϑΥϯτͷจࣈ͕ ࢖͏ൣғ ttf2mesh͸ ௥Ճ׽ࣈ໘ͷ׽ࣈͷάϦϑͷ ίʔυϙΠϯτΛऔΕͳ͍
  54. diff --git a/ttf2mesh.h b/ttf2mesh.h index 76e5765..abfcac9 100644 --- a/ttf2mesh.h +++

    b/ttf2mesh.h @@ -145,8 +145,8 @@ struct ttf_file { int nchars; /* number of the font characters */ int nglyphs; /* number of glyphs (usually less than nchars) */ - uint16_t *chars; /* utf16 codes array with nchars length */ - uint16_t *char2glyph; /* glyph indeces array with nchars length */ + uint32_t *chars; /* utf32 codes array with nchars length */ + uint32_t *char2glyph; /* glyph indeces array with nchars length */ ttf_glyph_t *glyphs; /* array of the font glyphs with nglyphs length */ const char *filename; /* full path and file name of the font */ uint32_t glyf_csum; /* 'glyf' table checksum (used by ttf_list_fonts) */ @@ -455,7 +455,7 @@ int ttf_list_match_id(ttf_t **list, const char *requirements, ...); * @param utf16_char Unicode character * @return Glyph index in glyphs array or -1 */ -int ttf_find_glyph(const ttf_t *ttf, uint16_t utf16_char); +int ttf_find_glyph(const ttf_t *ttf, uint32_t utf32_char); UnicodeͷίʔυϙΠϯτΛ 16bit੔਺ʹಥͬࠐΜͰ͍ΔॴΛ 32bit੔਺ʹஔ͖׵͑ͯ
  55. @@ -1318,6 +1335,59 @@ static int parse_fmt4(ttf_t *ttf, uint8_t *data,

    int dataSize, bool headers_only return TTF_DONE; } +static int parse_fmt12(ttf_t *ttf, uint8_t *data, int dataSize, bool headers_only) +{ + ttf_fmt12_t *tab; + int smgSize; + int i, j, k; + int maxGlyphID = 0; + int endGlyphID; + ttf_fmt12_smg_t *smgs; + + if (dataSize < (int)sizeof(ttf_fmt12_t)) return TTF_ERR_FMT; + tab = (ttf_fmt12_t *)data; + conv32(tab->length); + conv32(tab->numGroups); + if (tab->length > dataSize) + return TTF_ERR_FMT; Format 12ͷcmap͕͋ͬͨΒ ༏ઌతʹಡΉίʔυΛ௥Ճ
  56. GPUʹૹΔόΠφϦ ⋮ (−0.3515625,0.7128906) (−0.3203125,0.7988281) (−0.2734375,0.8535156) (−0.21484375,0.8847656) (−0.14453125,0.89453125) (−0.076171875,0.8847656) (−0.017578125,0.8515625) (0.029296875,0.7949219)

    (0.068359375,0.7128906) (0,0.6875) ⋮ 16 15 14 17 18 21 20 12 14 13 ⋮ ⋮ ⋮ ௖఺ΠϯσοΫε ௖఺഑ྻ શͯͷจࣈͷάϦϑΛ 1ͭͷόΠφϦʹؚΊ͍ͨ ͔͜͜Β ͜͜·Ͱ όΠφϦͷͲͷ෦෼͕ ͲͷάϦϑͷ෺͔Λ ผͷ৔ॴʹه࿥͢Δඞཁ͕͋Δ 1จࣈ෼
  57. ttf_t *font = nullptr; ttf_load_from_file( input.c_str(), &font, false); std::vector< float

    > vertices; std::unordered_map< std::pair< float, float >, std::uint32_t, pair_hash > vhash; std::vector< std::uint32_t > faces; std::vector< std::tuple< unsigned int, std::uint32_t, std::uint32_t > > glyphs; std::pair< float, float > min = std::make_pair( std::numeric_limits< float >::max(), std::numeric_limits< float >::max() ); std::pair< float, float > max = std::make_pair( std::numeric_limits< float >::min(), std::numeric_limits< float >::min() ); for( unsigned int glyph_id : tq::trange( font->nglyphs ) ) { ttf_mesh_t *out; if( font->glyphs[ glyph_id ].index && font->glyphs[ glyph_id ].outline ) { if( font->glyphs[ glyph_id ].symbol >= min_code && font->glyphs[ glyph_id ].symbol <= max_code ) { auto result = ttf_glyph2mesh( ttf2meshͰϑΥϯτΛಡΉ
  58. for( unsigned int glyph_id : tq::trange( font->nglyphs ) ) {

    ttf_mesh_t *out; if( font->glyphs[ glyph_id ].index && font->glyphs[ glyph_id ].outline ) { if( font->glyphs[ glyph_id ].symbol >= min_code && font->glyphs[ glyph_id ].symbol <= max_code ) { auto result = ttf_glyph2mesh( &font->glyphs[ glyph_id ], &out, TTF_QUALITY_LOW, TTF_FEATURE_IGN_ERR ); if( result == TTF_DONE) { std::vector< std::uint32_t > vertex_indices; for( unsigned int i = 0u; i != out->nvert; ++i ) { vertex_indices.push_back( get_index( vhash, vertices, out->vert[ i ].x, out->vert[ i ].y, align ) ); min.first = std::min( min.first, vertices[ vertex_indices.back() * 3u ] ); min.second = std::min( min.second, vertices[ vertex_indices.back() * 3u + 1u ] ); max.first = std::max( άϦϑͷ৘ใ͕ਖ਼͘͠औΕ͍ͯͯ ཉ͍͠ൣғͷจࣈͷάϦϑͩͬͨΒ
  59. auto result = ttf_glyph2mesh( &font->glyphs[ glyph_id ], &out, TTF_QUALITY_LOW, TTF_FEATURE_IGN_ERR

    ); if( result == TTF_DONE) { std::vector< std::uint32_t > vertex_indices; for( unsigned int i = 0u; i != out->nvert; ++i ) { vertex_indices.push_back( get_index( vhash, vertices, out->vert[ i ].x, out->vert[ i ].y, align ) ); min.first = std::min( min.first, vertices[ vertex_indices.back() * 3u ] ); min.second = std::min( min.second, vertices[ vertex_indices.back() * 3u + 1u ] ); max.first = std::max( max.first, vertices[ vertex_indices.back() * 3u ] ); max.second = std::max( max.second, vertices[ vertex_indices.back() * 3u + 1u ] ); } glyphs.push_back( std::make_tuple( glyph_id, faces.size(), 0u ) ); ࡾ֯ܗ෼ׂ ௖఺഑ྻʹಉ͡࠲ඪͷ௖఺͕طʹ͋ͬͨΒͦͷΠϯσοΫεΛฦ͢ ແ͔ͬͨΒ௖఺഑ྻʹ௖఺Λ௥Ճͯͦ͠ͷΠϯσοΫεΛฦ͢
  60. ); min.second = std::min( min.second, vertices[ vertex_indices.back() * 3u +

    1u ] ); max.first = std::max( max.first, vertices[ vertex_indices.back() * 3u ] ); max.second = std::max( max.second, vertices[ vertex_indices.back() * 3u + 1u ] ); } glyphs.push_back( std::make_tuple( glyph_id, faces.size(), 0u ) ); for( unsigned int i = 0u; i != out->nfaces; ++i ) { faces.push_back( vertex_indices[ out->faces[ i ].v1 ] ); faces.push_back( vertex_indices[ out->faces[ i ].v2 ] ); faces.push_back( vertex_indices[ out->faces[ i ].v3 ] ); } std::get< 2 >( glyphs.back() ) = faces.size() - std::get< 1 >( glyphs.back() ); ttf_free_mesh( out ); } } } } ௖఺ΠϯσοΫεΛ௥Ճ͢Δ ௖఺ΠϯσοΫεͷ͔͜͜Β ͜ͷ਺ͷ௖఺͕glyph_idͷάϦϑͰ͢
  61. nlohmann::json root; root[ "asset" ] = nlohmann::json::object({ { "generator", "ttf_to_gltf"

    }, { "version", "2.0" } }); root[ "buffers" ] = nlohmann::json::array(); root[ "buffers" ].push_back( nlohmann::json::object({ { "uri", "font.bin" }, { "byteLength", vertices.size() * sizeof( float ) + faces.size() * sizeof( std::uint32_t ) } }) ); root[ "bufferViews" ] = nlohmann::json::array(); for( unsigned int i = 0u; i != glyphs.size(); ++i ) { const auto begin = std::get< 1 >( glyphs[ i ] ); const auto size = std::get< 2 >( glyphs[ i ] ); root[ "bufferViews" ].push_back( nlohmann::json::object({ { "buffer", 0 }, { "target", 34963 }, { "byteOffset", glTFͷϔομ͸ී௨ͷJSONͳͷͰ ී௨ͷJSONΤϯίʔμͰ࡞ΕΔ
  62. root[ "bufferViews" ] = nlohmann::json::array(); for( unsigned int i =

    0u; i != glyphs.size(); ++i ) { const auto begin = std::get< 1 >( glyphs[ i ] ); const auto size = std::get< 2 >( glyphs[ i ] ); root[ "bufferViews" ].push_back( nlohmann::json::object({ { "buffer", 0 }, { "target", 34963 }, { "byteOffset", vertices.size() * sizeof( float ) + begin * sizeof( std::uint32_t ) }, { "byteLength", size * sizeof( std::uint32_t ) }, }) ); } root[ "bufferViews" ].push_back( nlohmann::json::object({ { "buffer", 0 }, { "target", 34962 }, { "byteLength", vertices.size() * sizeof( float ) }, }) ); root[ "accessors" ] = nlohmann::json::array(); for( unsigned int i = 0u; i != glyphs.size(); ++i ) { const auto begin = std::get< 1 >( glyphs[ i ] ); const auto size = std::get< 2 >( glyphs[ i ] ); ֤άϦϑͷઌ಄Λࢦ͢ όοϑΝϏϡʔΛ࡞Δ ࠷ޙʹ௖఺഑ྻͷ όοϑΝϏϡʔΛ࡞Δ
  63. root[ "accessors" ] = nlohmann::json::array(); for( unsigned int i =

    0u; i != glyphs.size(); ++i ) { const auto begin = std::get< 1 >( glyphs[ i ] ); const auto size = std::get< 2 >( glyphs[ i ] ); root[ "accessors" ].push_back( nlohmann::json::object({ { "bufferView", i }, { "componentType", 5125 }, // 32bit int { "type", "SCALAR" }, { "count", size } }) ); } root[ "accessors" ].push_back( nlohmann::json::object({ { "bufferView", glyphs.size() }, { "componentType", 5126 }, // 32bit float { "type", "VEC3" }, { "count", vertices.size() / 3u }, { "min", nlohmann::json::array({ min.first, min.second, 0.f })}, { "max", nlohmann::json::array({ max.first, max.second, 0.f })} }) ); ֤όοϑΝϏϡʔΛࢦ͢ ΞΫηαΛ࡞Δ ௖఺഑ྻΛࢦ͢ΞΫηαΛ࡞Δ
  64. for( unsigned int i = 0u; i != glyphs.size(); ++i

    ) { const auto glyph_id = std::get< 0 >( glyphs[ i ] ); root[ "meshes" ].push_back( nlohmann::json::object({ { "primitives", nlohmann::json::array({ nlohmann::json::object({ { "attributes", nlohmann::json::object({ { "POSITION", glyphs.size() } })}, { "indices", i } })})}, { "name", std::to_string( glyph_id ) } }) ); } root[ "nodes" ] = nlohmann::json::array(); for( unsigned int i = 0u; i != glyphs.size(); ++i ) { const auto glyph_id = std::get< 0 >( glyphs[ i ] ); root[ "nodes" ].push_back( nlohmann::json::object({ { "mesh", i }, { "name", std::to_string( glyph_id ) } }) ); } root[ "scene" ] = 0; root[ "scenes" ].push_back( nlohmann::json::object({ { "nodes", nlohmann::json::array() } ͜ͷΞΫηα͔Β ௖఺ΠϯσοΫεΛಡΉ ͜ͷΞΫηα͔Β ௖఺഑ྻΛಡΈ ͦΜͳϝογϡΛ 1ؚͭΉϊʔυ ϊʔυ໊͸glyph_idʹ͓ͯ͘͠
  65. { std::fstream bin( bin_filename, std::ios::out|std::ios::binary ); bin.write( reinterpret_cast< const char*

    >( vertices.data() ), vertices.size() * sizeof( float ) ); bin.write( reinterpret_cast< const char* >( faces.data() ), faces.size() * sizeof( std::uint32_t ) ); } { if( format == "json" ) { std::fstream json( json_filename, std::ios::out ); json << root.dump( 2 ) << std::endl; } else if( format == "bson" ) { std::fstream json( json_filename, std::ios::out|std::ios::binary ); auto serialized = nlohmann::json::to_bson( root ); json.write( reinterpret_cast< const char* >( serialized.data() ), serialized.size() ); } GPUʹૹΔόΠφϦΛ ϑΝΠϧʹు͘ ϔομͷJSONΛ ϑΝΠϧʹు͘
  66. font command_buffer_recorder_t::load_font( std::filesystem::path path, const std::shared_ptr< allocator_t > &allocator )

    { fx::gltf::Document doc = fx::gltf::LoadFromText( path.string() ); if( doc.buffers.size() != 1u ) throw invalid_font(); auto buffer_path = std::filesystem::path( doc.buffers[ 0 ].uri ); if( buffer_path.is_relative() ) buffer_path = path.parent_path() / buffer_path; auto buffer = load_buffer_from_file( allocator, buffer_path.string(), vk::BufferUsageFlagBits::eVertexBuffer|vk::BufferUsageFlagBits::eIndexBuffer ); const auto vertex_buffer_view = std::find_if( doc.bufferViews.begin(), doc.bufferViews.end(), []( const auto &v ) { return v.target == fx::gltf::BufferView::TargetType::ArrayBuffer; } ); if( vertex_buffer_view == doc.bufferViews.end() ) throw invalid_font(); const std::uint32_t vertex_buffer_offset = vertex_buffer_view->byteOffset; const std::uint32_t vertex_buffer_length = vertex_buffer_view->byteLength; glTFΛύʔεͯ͠ όΠφϦΛGPUʹૹΔ
  67. if( accessor.type != fx::gltf::Accessor::Type::Scalar ) throw invalid_font(); if( accessor.bufferView >=

    doc.bufferViews.size() ) throw invalid_font(); const auto &buffer_view = doc.bufferViews[ accessor.bufferView ]; if( buffer_view.buffer != 0u ) throw invalid_font(); if( buffer_view.target != fx::gltf::BufferView::TargetType::ElementArrayBuffer ) throw invalid_font(); const auto offset = buffer_view.byteOffset - vertex_index_offset; constexpr static std::uint32_t face_size = ( sizeof( std::uint32_t ) * 3u ); if( offset % face_size ) throw invalid_font(); if( buffer_view.byteLength % face_size ) throw invalid_font(); hash.insert( std::make_pair( gid, glyph_index{ std::uint32_t( offset / sizeof( std::uint32_t ) ), std::uint32_t( buffer_view.byteLength / sizeof( std::uint32_t ) ) } ) ); } ͋Δglyph_id͕ཁٻ͞Εͨ࣌ʹ ௖఺ΠϯσοΫεͷ Ͳ͔͜ΒͲ͜·ͰΛ࢖͏΂͖͔Λه࿥ͨ͠ unordered_map
  68. CPU͔Β΋GPU͔Β΋ݟ͑ΔϝϞϦ GPU CPU ௕ํܗ × 500 Πϯελϯγϯά 1ίϚϯυͰಉ͡τϙϩδͷਤܗΛେྔʹඳ͘ ௕ํܗ0ͷ഑ஔ ௕ํܗ1ͷ഑ஔ

    ௕ํܗ2ͷ഑ஔ ௕ํܗ3ͷ഑ஔ ⋮ ݸʑͷจࣈ͸τϙϩδ͕ҟͳΔҝ ΠϯελϯγϯάͰ·ͱΊͯඳ͚ͳ͍
  69. CPU͔Β΋GPU͔Β΋ݟ͑ΔϝϞϦ GPU CPU Indirect Draw 1ίϚϯυͰҟͳΔτϙϩδͷਤܗΛେྔʹඳ͘ άϦϑ0ͷ഑ஔ άϦϑ1ͷ഑ஔ άϦϑ2ͷ഑ஔ άϦϑ3ͷ഑ஔ

    ⋮ άϦϑ0ͷ௖఺ͷಡΈํ άϦϑ1ͷ௖఺ͷಡΈํ άϦϑ2ͷ௖఺ͷಡΈํ άϦϑ3ͷ௖఺ͷಡΈํ 
 ⋮ ͜͜ʹྻڍͨ͠΍ͭඳ͍ͯ
  70. void text_renderer::add_glyph( std::uint32_t glyph_id, float x, float y, float size,

    const std::array< float, 4u > &color, std::uint16_t depth, std::uint16_t texid, std::uint16_t semid ) { auto glyph_info = font_info.index.find( glyph_id ); if( glyph_info != font_info.index.end() ) { const auto instance_id = instance_count++; uniforms_host.rects[ instance_id ].offset[ 0 ] = x * scale; uniforms_host.rects[ instance_id ].offset[ 1 ] = y * scale; uniforms_host.rects[ instance_id ].extent[ 0 ] = size * scale; uniforms_host.rects[ instance_id ].extent[ 1 ] = size * scale; uniforms_host.rects[ instance_id ].color = glm::vec4( color[ 0 ], color[ 1 ], color[ 2 ], color[ 3 ] ); uniforms_host.rects[ instance_id ].depth = depth; จࣈͷ௥ՃΛཁٻ͞ΕͨΒ ͦͷάϦϑ͕ GPUʹૹͬͨσʔλͷதʹ͋Δ͔Λௐ΂ͯ
  71. if( glyph_info != font_info.index.end() ) { const auto instance_id =

    instance_count++; uniforms_host.rects[ instance_id ].offset[ 0 ] = x * scale; uniforms_host.rects[ instance_id ].offset[ 1 ] = y * scale; uniforms_host.rects[ instance_id ].extent[ 0 ] = size * scale; uniforms_host.rects[ instance_id ].extent[ 1 ] = size * scale; uniforms_host.rects[ instance_id ].color = glm::vec4( color[ 0 ], color[ 1 ], color[ 2 ], color[ 3 ] ); uniforms_host.rects[ instance_id ].depth = depth; uniforms_host.rects[ instance_id ].texid = texid; uniforms_host.rects[ instance_id ].semantic[ 0 ] = semid; uniforms_host.rects[ instance_id ].semantic[ 1 ] = 0; indirect_host[ instance_id ].indexCount = glyph_info->second.vertex_count; indirect_host[ instance_id ].instanceCount = 1u; indirect_host[ instance_id ].firstIndex = glyph_info->second.vertex_offset; indirect_host[ instance_id ].vertexOffset = 0u; indirect_host[ instance_id ].firstInstance = instance_id; } } άϦϑͷ৭ͱ഑ஔ άϦϑͷ௖఺ΛͲͷҐஔ͔ΒಡΉ͔
  72. void text_renderer::draw( gct::command_buffer_recorder_t &recorder ) { recorder.bind_pipeline( pipeline ); recorder.bind_descriptor_set(

    vk::PipelineBindPoint::eGraphics, pipeline_layout, descriptor_set ); recorder.bind_vertex_buffer( font_info.buffer ); recorder.bind_index_buffer( font_info.buffer, font_info.offset, vk::IndexType::eUint32 ); recorder->drawIndexedIndirect( **indirect_device, 0, instance_count, sizeof( VkDrawIndexedIndirectCommand ) ); } ϑΥϯτͷ௖఺഑ྻΛ࢖͏ ϑΥϯτͷ௖఺ΠϯσοΫεΛ࢖͏ ͜͜ʹஔ͍ͯ͋Δ௖఺ͷಡΈํʹैͬͯ ͜ͷ਺ͷάϦϑΛඳը
  73. όΠτྻ ୯७ʹฒ΂Δͱ ਖ਼͍͠දࣔ H e l l o 48 65

    6c 6c 6f Hello Hello ӳޠ จࣈαΠζ෼ͮͭӈʹਐΉ
  74. όΠτྻ ୯७ʹฒ΂Δͱ ਖ਼͍͠දࣔ ͜ Μ ʹ ͪ ͸ e3 81

    93 e3 82 93 e3 81 ab e3 81 a1 e3 81 af ͜Μʹͪ͸ ͜Μʹͪ͸ ೔ຊޠ จࣈαΠζ෼ͮͭӈʹਐΉ
  75. όΠτྻ ୯७ʹฒ΂Δͱ ਖ਼͍͠දࣔ ส ว ั ส ด ี e0

    b8 aa e0 b8 a7 e0 b8 b1 e0 b8 aa e0 b8 94 e0 b8 b5 ส วั ส ดี ส วั ส ดี ্ʹ৐͔ͬͬͯΔ ด ส ว ส ั ี λΠޠ ෳ਺ͷίʔυϙΠϯτͷฒͼͰάϦϑΛඳ͘΂͖Ґஔ͕มΘΔ
  76. όΠτྻ ୯७ʹฒ΂Δͱ ਖ਼͍͠දࣔ न म स ् त े e0

    a4 a8 e0 a4 ae e0 a4 b8 e0 a5 8d e0 a4 a4 e0 a5 87 नम स्त े नमस ् त े नम स्ते ࣍ͷίʔυϙΠϯτͱ߹ମͯ͠ܗ͕มΘ͍ͬͯΔ ώϯσΟʔޠ ෳ਺ͷίʔυϙΠϯτͷฒͼͰඳ͘΂͖άϦϑ͕มΘΔ
  77. όΠτྻ ୯७ʹฒ΂Δͱ ਖ਼͍͠දࣔ ש ל ו ם d7 a9 d7

    9c d7 95 d7 9d םולש שלום םולש ӈ͔Βࠨ ϔϒϥΠޠ 1จࣈඳ͍ͨΒࠨʹਐΉ
  78. όΠτྻ ୯७ʹฒ΂Δͱ ਖ਼͍͠දࣔ م ر ح ب ً ط d9

    85 d8 b1 d8 ad d8 a8 d9 8b d8 a7 ﺎًﺑﺣرﻣ مرحبً ط ﺎًﺑﺣرﻣ ӈ͔Βࠨ͔ͭ࣍ͷίʔυϙΠϯτͱ߹ମͯ͠ผͷάϦϑʹมԽ ΞϥϏΞޠ
  79. ส วั ส ดี HarfBuzz͕΍ͬͯ͘ΕΔࣄ ࣍ͷจࣈΛ ඳ͘΂͖ҐஔΛٻΊΔ नम स्ते स

    ͲͷάϦϑͰ ඳ͘΂͖͔ΛٻΊΔ ࠓ͙͢μ΢ϯϩʔ υ HarfBuzz͕΍ͬͯ͘Εͳ͍ࣄ ௕͍จࣈྻͷંΓฦ͠ םולש שלום จࣈྻͷਐߦํ޲ͷ൑அ ? ? จࣈͷϨϯμϦϯά םולש ৗʹࠨ͔Β ॲཧͰ͖ΔΑ͏ʹฒ΂ସ͑
  80. ส วั ส ดี HarfBuzz͕΍ͬͯ͘ΕΔࣄ ࣍ͷจࣈΛ ඳ͘΂͖ҐஔΛٻΊΔ नम स्ते स

    ͲͷάϦϑͰ ඳ͘΂͖͔ΛٻΊΔ ࠓ͙͢μ΢ϯϩʔ υ HarfBuzz͕΍ͬͯ͘Εͳ͍ࣄ ௕͍จࣈྻͷંΓฦ͠ םולש שלום จࣈྻͷਐߦํ޲ͷ൑அ ? ? จࣈͷϨϯμϦϯά םולש ৗʹࠨ͔Β ॲཧͰ͖ΔΑ͏ʹฒ΂ସ͑ ࠨ͔Βӈʹॻ͘ݴޠͳͷ͔ ӈ͔Βࠨʹॻ͘ݴޠͳͷ͔͸ ΞϓϦέʔγϣϯ͕൑அ HarfBuzz͸ࢦఆ͞Εͨ޲͖ʹจࣈΛฒ΂Δॲཧ͚ͩΛ͢Δ
  81. U_CAPI UBiDiDirection U_EXPORT2 ubidi_getBaseDirection(const UChar *text, int32_t length ); unicode/ubidi.h

    ͜ͷจࣈྻͬͯͲͬͪ޲͖? UBiDiDirection::UBIDI_LTR ࠨ͔ΒӈͷςΩετͰ࢝·Δ UBiDiDirection::UBIDI_RTL ӈ͔ΒࠨͷςΩετͰ࢝·Δ UBiDiDirection::NEUTRAL จࣈྻۭ΍Μ… ฦΓ஋
  82. An apple is called חופת in Hebrew. .תילגנאב תארקנ חופת

    apple ࠨ͔ΒӈͷςΩετͷதʹӈ͔ΒࠨͷςΩετ͕͍ࠞͬͯ͟Δ ӈ͔ΒࠨͷςΩετͷதʹࠨ͔ΒӈͷςΩετ͕͍ࠞͬͯ͟Δ ӈ͔Βࠨʹॻ͘ݴޠͰ΋௨ৗ਺ࣈ͸ࠨ͔Βӈʹॻ͘ͷͰ ӈ͔ΒࠨͷςΩετʹ͸೔ৗతʹࠨ͔ΒӈͷςΩετ͕ࠞ͟Δ
  83. auto bidi = ubidi_open(); if( !bidi ) std::abort(); ubidi_setPara( bidi,

    utf16, utf16_length, base_direction == UBIDI_RTL, nullptr, &error ); if( U_FAILURE( error ) ) std::abort(); const auto run_count = ubidi_countRuns( bidi, &error ); for( unsigned int i = 0u; i != run_count; ++i ) { int32_t start = 0u; int32_t length = 0u; auto bidi_dir = ubidi_getVisualRun( bidi, i, &start, &length ); if( bidi_dir == UBIDI_NEUTRAL ) { std::abort(); } if( bidi_dir == UBIDI_MIXED ) { std::abort(); } ICUͰ ਐߦํ޲͕ಉ͡ൣғͰจࣈྻΛ۠੾Δ ൣғͷ਺Λऔಘ ͜ͷൣғ͸Ͳͬͪ޲͖?
  84. ส วั ส ดี HarfBuzz͕΍ͬͯ͘ΕΔࣄ ࣍ͷจࣈΛ ඳ͘΂͖ҐஔΛٻΊΔ नम स्ते स

    ͲͷάϦϑͰ ඳ͘΂͖͔ΛٻΊΔ ࠓ͙͢μ΢ϯϩʔ υ HarfBuzz͕΍ͬͯ͘Εͳ͍ࣄ ௕͍จࣈྻͷંΓฦ͠ םולש שלום จࣈྻͷਐߦํ޲ͷ൑அ ? ? จࣈͷϨϯμϦϯά םולש ৗʹࠨ͔Β ॲཧͰ͖ΔΑ͏ʹฒ΂ସ͑ ௕͍ςΩετ͸ંΓฦͯ͠දࣔ͠ͳ͚Ε͹ͳΒͳ͍ Unicodeจࣈྻʹ͸ંΓฦͯ͠͸͍͚ͳ͍Օॴ͕͋Δ
  85. English string should not break in the middle of a

    word. ೔ຊޠͷจࣈྻͰ͸ࣺͯԾ໊͸ߦ಄ʹ࣋ͬ ͯ͘Δ΂͖Ͱ͸ͳ͍ ͜͜ͳΒંΓฦͯ͠Α͍ ͜͜ͰંΓฦͯ͠͸͍͚ͳ͍ ͜͜ͳΒંΓฦͯ͠Α͍ ͜͜ͰંΓฦͯ͠͸͍͚ͳ͍ ส วั ส ดี 1จࣈͱݟ၏͞ΕΔγʔέϯεͷ్தͰંΓฦ͢΂͖Ͱ͸ͳ͍
  86. auto iter = ubrk_open( UBRK_LINE, locale, utf16nfc + start, length,

    &error ); para.push_back( run{} ); para.back().is_rtl = is_rtl; std::array< UChar32, 512u > utf32 = { 0 }; int32_t cur = 0u; int32_t next; while( ( next = ubrk_next( iter ) ) != UBRK_DONE ) { std::int32_t utf32_length = 0u; u_strToUTF32( utf32.data(), utf32.size(), &utf32_length, utf16nfc + start + cur, next - cur, &error ); if( U_FAILURE( error ) ) std::abort(); if( utf32_length != 0u ) { auto script_code = uscript_getScript( utf32[ 0 ], &error ); bool newline = is_newline( utf32[ 0 ] ); if( U_FAILURE( error ) ) std::abort(); ubrkΛ࢖͏ͱUnicodeจࣈྻΛ ʮվߦΛڬΜͰ͸͍͚ͳ͍ʯ୯ҐͰ۠੾Δࣄ͕Ͱ͖Δ
  87. ICU ubidi ICU ubrk HarfBuzz จࣈྻ LTRจࣈྻ RTLจࣈྻ LTRจࣈྻ LTRจࣈྻ

    RTLจࣈྻ LTRจࣈྻ ୯ޠ ୯ޠ ୯ޠ ୯ޠ ୯ޠ ୯ޠ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ͜͜ͰંΓฦͯ͠େৎ෉ϚʔΫ
  88. ICU ubrk HarfBuzz LTRจࣈྻ RTLจࣈྻ LTRจࣈྻ ୯ޠ ୯ޠ ୯ޠ ୯ޠ

    ୯ޠ ୯ޠ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ࣈ ߦͷંΓฦ͠ ը໘্Ͱͷจࣈͷ഑ஔ
  89. typedef struct hb_glyph_position_t { hb_position_t x_advance; hb_position_t y_advance; hb_position_t x_offset;

    hb_position_t y_offset; /*< private >*/ hb_var_int_t var; } hb_glyph_position_t; A B x_offset x_advance ΧʔιϧͷҐஔ͔Β ͲΕ͚ͩͣΕͨ৔ॴʹ จࣈΛඳ͘΂͖͔ จࣈΛඳ͍ͨޙͰ ΧʔιϧΛͲΕ͚ͩ ਐΊΔ΂͖͔
  90. 1pt = 1 72 in ≃ 0.3528mm 1pt = 0.3514mm

    ݩʑͷఆٛ JISن֨Ͱͷఆٛ จࣈͷେ͖͞ͷ୯Ґ ϙΠϯτ(pt) ͲͪΒʹͤΑݴ͑Δ͜ͱ ը໘ͷ෺ཧతͳେ͖͞ͷ৘ใ͕ඞཁ
  91. VESA ENHANCED EXTENDED DISPLAY IDENTIFICATION DATA STANDARD (Defines EDID Structure

    Version 1, Revision 4) 91ϖʔδΑΓ EDID 1.4ʹ͸1cm୯Ґͷ ը໘ͷେ͖͕͞ ؚ·Ε͍ͯΔ VESA DisplayID Standard Version 2.1 47ϖʔδΑΓ DisplayIDʹ͸ 1mm·ͨ͸0.1mm୯Ґͷ ը໘ͷେؚ͖͕͞·Ε͍ͯΔ σΟεϓϨΠ͸ࣗ਎ͷ෺ཧతͳେ͖͞Λ஻͍ͬͯΔ ϓϩδΣΫλ౳αΠζΛఆٛͰ͖ͳ͍ػثͰ͸ ஻͍ͬͯͳ͍ࣄ΋͋Δ
  92. typedef struct VkDisplayPropertiesKHR { VkDisplayKHR display; const char* displayName; VkExtent2D

    physicalDimensions; VkExtent2D physicalResolution; VkSurfaceTransformFlagsKHR supportedTransforms; VkBool32 planeReorderPossible; VkBool32 persistentContent; } VkDisplayPropertiesKHR; VulkanͰσΟεϓϨΠͷ෺ཧతͳେ͖͞ΛऔΕΔ (mm୯Ґ) औΕͳ͍࣌͸ͱΓ͋͑ͣ96dpiͩͱࢥ͏ Windows compatible͙͠͞Λ͓ͯ͜͠͏
  93. d7 97 d7 a6 d7 99 d7 9c 20 28

    d7 a9 d7 9d 20 d7 9e d7 93 d7 a2 d7 99 3a ל ח צ י ۭന ( ͜ͷׅހ͸ASCIIίʔυͷׅހͰ ࠨ͔Βӈʹॻ͘ςΩετʹ͓͍ͯ͸ӈΛ޲͍͍ͯΔ͕ ӈ͔Βࠨʹॻ͘ςΩετͰ͋Δͱ͍͏ࢦ͕ࣔͳ͞Ε͍ͯΔҝ HarfBuzz͸UAX #9 §3.4 L4ʹैͬͯάϦϑΛ)ʹஔ͖׵͑Δ
  94. struct GLBHeader { uint32_t magic{}; uint32_t version{}; uint32_t length{}; ChunkHeader

    jsonHeader{}; }; constexpr uint32_t DefaultMaxBufferCount = 8; constexpr uint32_t DefaultMaxMemoryAllocation = 32 * 4 * 1024 * 1024; constexpr std::size_t HeaderSize{ sizeof(GLBHeader) }; constexpr std::size_t ChunkHeaderSize{ sizeof(ChunkHeader) }; constexpr uint32_t GLBHeaderMagic = 0x46546c67u; constexpr uint32_t GLBChunkJSON = 0x4e4f534au; constexpr uint32_t GLBChunkBIN = 0x004e4942u; constexpr char const * const MimetypeApplicationOctet = "data:application/octet-stre constexpr char const * const MimetypeGLTFBuffer = "data:application/gltf-buffer;base constexpr char const * const MimetypeImagePNG = "data:image/png;base64"; constexpr char const * const MimetypeImageJPG = "data:image/jpeg;base64"; fx/gltf.h 43985ϊʔυΛؚΉڊେͳglTFΛ glTFύʔαͷfx-gltf͕ҟৗͳglTFͱ൑அ͢Δ όοϑΝͷ࠷େαΠζΛ4ഒʹ͢ΔͱಡΊΔ
  95. Ϣʔβۭؒ Ϣʔβۭؒ Χʔωϧۭؒ Linux evdev libinput Ϛ΢ε͕ಈ͖·ͨ͠ ΞϓϦέʔγϣϯ xcb αʔό

    ͓લɺΫϦοΫ͞ΕͯΔͧ Xαʔό͸࠷΋ԼͷϨΠϠʔͰ͸ͳ͍ HIDυϥΠό Ϛ΢εͷࠨϘλϯ͕ԡ͞Ε·ͨ͠
  96. void libinput_t::libinput_internal_t::poll() { auto udev = udev_new(); libinput_interface interface; interface.open_restricted

    = open_restricted; interface.close_restricted = close_restricted; auto li = libinput_udev_create_context(&interface, nullptr, udev); if( li == nullptr ) { std::abort(); } if( libinput_udev_assign_seat(li, "seat0") != 0 ) { std::abort(); } auto libinput_fd = libinput_get_fd( li ); while( 1 ) { int e = 0u; if( ( e = libinput_dispatch( li ) ) != 0 ) { throw std::system_error( std::make_error_code( std::errc( -e ) ), "libinput_t::poll : initial libinput_dispatch failed." ); } libinput_event *raw_event = nullptr; while ((raw_event = libinput_get_event( li )) != nullptr) { std::unique_ptr< libinput_event, libinput_event_deleter > event( raw_event ); const auto type = libinput_event_get_type( raw_event ); if( type == LIBINPUT_EVENT_DEVICE_ADDED ) { udevΛ։͘ libinputͷίϯςΩετΛ࡞Δ γʔτΛ࡞Δ Πϕϯτ͋ͬͨΒ͘Ε
  97. ԿΒ͔ͷೖྗΠϕϯτ͕ى͜Δͱ ͜ͷfdʹ௨஌͕͘ΔͷͰ epollͰ଴ͭ if( ( e = libinput_dispatch( li )

    ) != 0 ) { throw std::system_error( std::make_error_code( std::errc( -e ) ), "libinput_t::poll : initial libinput_dispatch failed." ); } } try { auto event = sched::wait( epoll_notifier->epoll( EPOLLIN, libinput_fd ) | epoll_notifier->epoll( EPOLLIN, set_fd ) ); if( event.data.fd == libinput_fd ) { } else if( event.data.fd == set_fd ) { std::uint64_t temp; if( read( set_fd, &temp, sizeof( temp ) ) < 0 ) { throw std::system_error( std::make_error_code( std::errc( errno ) ), "libinput_t::call : broken event notifier." ); } if( !end ) { } else {
  98. thread_pool->add_co( [ thread_pool=thread_pool, pointer=pointer, &cursor_x, &cursor_y, &cursor_moved ]() { while(

    1 ) { for( auto e: gct::sched::wait( pointer->get_future() ) ) { if( e.event.index() == 0 ) { auto &m = std::get< gct::input::pointer_moved >( e.event ); cursor_moved = true; cursor_x += m.dx; cursor_y += m.dy; } } } } ); Ϛ΢ε͕ಈ͍ͨΠϕϯτΛरͬͯ
  99. if( fb.initial || cursor_moved ) { { auto recorder =

    sync.command_buffer->begin(); if( cursor_moved ) { cursor_moved = false; window.reset(); window.add_rectangle( vk::Rect2D{ vk::Offset2D{ cursor_x, height-cursor_y }, vk::Extent2D{ 64u, 64u } }, gct::srgb_oetf( gct::html_color( 0xFFFFFF ) ), 1u, true, 0u, 0u, 0u ); for( auto &f: framebuffers ) { f.initial = true; } } window( recorder ); recorder.blit( window.get_color(), fb.color ); Χʔιϧ͕ಈ͍͍ͯͨΒ ৽͍͠ҐஔʹΧʔιϧΛ࠶ඳը
  100. thread_pool->add_co( [thread_pool=thread_pool,keyboard=keyboard]() { while( 1 ) { for( auto c:

    wait( keyboard->get_future() ) ) { std::cout << c.code; if( c.state == gct::input::key_state::released ) std::cout << " released" << std::endl; else std::cout << " pressed" << std::endl; } } } ); ΩʔϘʔυͷΠϕϯτΛरͬͯ த਎Λදࣔ
  101. $ ./src/example/libinput_keycode/libinput_keycode 36 pressed 36 released 37 pressed 37 released

    38 pressed 38 released 39 pressed 39 released 39 pressed 39 released 39 pressed 38 pressed 39 released 38 released 53 pressed 52 pressed 52 released 50 pressed 51 pressed 50 released ͜ͷ൪߸͸Կ?
  102. #define KEY_Q 16 #define KEY_W 17 #define KEY_E 18 #define

    KEY_R 19 #define KEY_T 20 #define KEY_Y 21 #define KEY_U 22 #define KEY_I 23 #define KEY_O 24 #define KEY_P 25 #define KEY_LEFTBRACE 26 #define KEY_RIGHTBRACE 27 #define KEY_ENTER 28 #define KEY_LEFTCTRL 29 #define KEY_A 30 #define KEY_S 31 #define KEY_D 32 #define KEY_F 33 #define KEY_G 34 #define KEY_H 35 #define KEY_J 36 /usr/include/linux/input-event-codes.h LinuxͷೖྗΠϕϯτͷ൪߸
  103. #define KEY_Q 16 #define KEY_W 17 #define KEY_E 18 #define

    KEY_R 19 #define KEY_T 20 #define KEY_Y 21 #define KEY_U 22 #define KEY_I 23 #define KEY_O 24 #define KEY_P 25 #define KEY_LEFTBRACE 26 #define KEY_RIGHTBRACE 27 #define KEY_ENTER 28 #define KEY_LEFTCTRL 29 #define KEY_A 30 #define KEY_S 31 #define KEY_D 32 #define KEY_F 33 #define KEY_G 34 #define KEY_H 35 #define KEY_J 36 /usr/include/linux/input-event-codes.h Πϕϯτ KEY_A ͕ى͖ͨΒ ΩʔϘʔυͷA͕ԡ͞Εͨͱ͍͏ҙຯ ͱ͸ݶΒͳ͍
  104. 2 3 4 5 6 7 8 9 10 11

    12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 56 57 58 100 97 139 125 126 KEY_A(30) ANSIΩʔϘʔυ(US഑ྻ)ͷA͕͋ΔҐஔͷ Ωʔ͕ԡ͞Εͨͱ͍͏ҙຯ
  105. KEY_A(30) 1 2 3 4 5 6 7 8 9

    0 A Z E R T Y U I O P Q S D F G H J K L M W X C V B N ISOΩʔϘʔυ ϑϥϯεޠ AZERTY഑ྻͷ৔߹ KEY_A͸Q͕ԡ͞Εͨͱ͍͏ҙຯ
  106. xkbcommon X Window System͔Β X Keyboard Extension(xkb)ͷ ෦෼͚ͩΛ੾Γग़ͨ͠΋ͷ /usr/share/X11/xkb/ ͋ͨΓʹస͕͍ͬͯΔ

    ม׵දΛ࢖ͬͯ ͲͷҐஔͷΩʔ͕ԡ͞Ε͔ͨΛ ԿͷจࣈͷΩʔ͕ೖྗ͞Ε͔ͨʹ ม׵͢Δ https://xkbcommon.org/
  107. $ localectl System Locale: LANG=ja_JP.utf8 VC Keymap: jp106 X11 Layout:

    jp X11 Model: jp106 X11 Variant: OADG109A X11 Options: terminate:ctrl_alt_bksp ΩʔϘʔυϨΠΞ΢τͷ৘ใ ͜ΕΞϓϦέʔγϣϯ͔ΒͲ͏΍ͬͯऔΔͷ?
  108. auto dbus_locale = sdbus::createProxy( "org.freedesktop.locale1", "/org/freedesktop/locale1" ); auto [layout,model,variant,options] =

    wait( gct::dbus::call< sdbus::Variant >( dbus_locale->callMethodAsync( "Get" ) .onInterface( "org.freedesktop.DBus.Properties" ) .withArguments( "org.freedesktop.locale1", "X11Layout" ) ) & gct::dbus::call< sdbus::Variant >( dbus_locale->callMethodAsync( "Get" ) .onInterface( "org.freedesktop.DBus.Properties" ) .withArguments( "org.freedesktop.locale1", "X11Model" ) ) & gct::dbus::call< sdbus::Variant >( dbus_locale->callMethodAsync( "Get" ) .onInterface( "org.freedesktop.DBus.Properties" ) .withArguments( "org.freedesktop.locale1", "X11Variant" ) ) & gct::dbus::call< sdbus::Variant >( dbus_locale->callMethodAsync( "Get" ) .onInterface( "org.freedesktop.DBus.Properties" ) .withArguments( "org.freedesktop.locale1", "X11Options" ) ) ); γεςϜόεͷ org.freedesktop.locale1ͷ /org/freedesktop/locale1ʹܨ͍Ͱ ϓϩύςΟΛर͏ͱऔΕΔ
  109. auto raw_ctx = xkb_context_new( XKB_CONTEXT_NO_FLAGS ); if( !raw_ctx ) {

    throw std::system_error( std::make_error_code( std::errc::not_supported ), "xkbcommon_t::xkbcommon_t : xkb_context_new failed." ); } ctx.reset( raw_ctx ); auto raw_keymap = xkb_keymap_new_from_names( ctx.get(), &names, XKB_KEYMAP_COMPILE_NO_FLAGS ); if( !raw_keymap ) { throw std::system_error( std::make_error_code( std::errc::not_supported ), "xkbcommon_t::xkbcommon_t : xkb_keymap_new_from_names failed." ); } keymap.reset( raw_keymap ); auto raw_state = xkb_state_new( keymap.get() ); if( !raw_state ) { throw std::system_error( std::make_error_code( std::errc::not_supported ), "xkbcommon_t::xkbcommon_t : xkb_state_new failed." ); } state.reset( raw_state ); xkbͷίϯςΩετΛ࡞Δ ΩʔϘʔυϨΠΞ΢τͷ໊લ͔Β ΩʔϚοϓΛݟ͚ͭΔ ΩʔϘʔυͷ ঢ়ଶϚγϯΛ࡞Δ
  110. std::shared_ptr< gct::input::input_buffer< gct::input::libinput_event_category::keyboard_event, gct::input::xkb_key_event > > keyboard( new gct::input::input_buffer< gct::input::libinput_event_category::keyboard_event,

    gct::input::xkb_key_event >( libinput, []( auto &dest, auto *event ) { const auto key = libinput_event_keyboard_get_key( event ); const auto time = std::chrono::microseconds( libinput_event_keyboard_get_time_usec( event ) ); const auto state = gct::input::libinput_key_state_to_gct_key_state( libinput_event_keyboard_get_key_state( event ) ); dest.push_back( gct::input::xkb_key_event{ 0u, key + 8u, state, time } ); } ) ); xkbͷΩʔϘʔυΠϕϯτͷ൪߸͸ LinuxͷΩʔϘʔυΠϕϯτͷ൪߸+8 libinput͔ΒདྷͨΠϕϯτΛ xkbcommonʹ౉͢
  111. thread_pool->add_co( [xkb_state=xkb_state,&current_text,&text_changed,&text_guard]() { try { while( 1 ) { for(

    auto c: wait( xkb_state->get_future() ) ) { if( c.state == gct::input::key_state::pressed ) { if( 0x20 <= c.sym && c.sym <= 0x7F ) { std::scoped_lock< std::mutex > lock( text_guard ); current_text += char( c.sym ); std::cout << current_text << std::endl; text_changed = true; } else if( c.sym == 0xFF08 ) { if( !current_text.empty() ) { std::scoped_lock< std::mutex > lock( text_guard ); current_text.pop_back(); std::cout << current_text << std::endl; text_changed = true; } } else if( c.sym == 0xFF0d ) { std::scoped_lock< std::mutex > lock( text_guard ); current_text += '\n'; std::cout << current_text << std::endl; text_changed = true; } xkbcommon͔Β ೖྗ͞ΕͨจࣈΛ΋Β͏
  112. $ ./src/example/libinput_keycode/libinput_keysym H He Hel Hell Hello Hello, Hello, Hello,

    W Hello, Wo Hello, Wor Hello, Worl Hello, World Hello, World! Hello, World!a Hello, World! Hello, World Hello, Worl Hello, Wor Hello, Wo Hello, W ʮԿͷจࣈ͕ೖྗ͞Ε͔ͨʯ ͕औΕΔ
  113. Ϣʔβۭؒ Ϣʔβۭؒ Χʔωϧۭؒ Linux evdev libinput ΞϓϦέʔγϣϯ xcb αʔό IMMODULE͕ΩʔϘʔυΠϕϯτΛ

    ೖྗϝιουʹ఻͑Δ HIDυϥΠό Qt΍GTK+ IMMODULE Ϣʔβۭؒ ೖྗϝιου ม׵Τϯδϯ
  114. fcitx5::fcitx5_internal::fcitx5_internal( const std::shared_ptr< sched::thread_pool_t > &tp ) : thread_pool( tp

    ) { conn = sdbus::createDefaultBusConnection(); input_method = sdbus::createProxy( *conn, "org.fcitx.Fcitx5", "/org/freedesktop/portal/inputmethod" ); conn->enterEventLoopAsync(); } void fcitx5::fcitx5_internal::run() { thread_pool->add_co( [self=shared_from_this()]() { self->poll(); } ); } void fcitx5::fcitx5_internal::poll() { auto [version] = sched::wait( gct::dbus::call< std::uint32_t >( input_method->callMethodAsync( "Version" ) .onInterface( "org.fcitx.Fcitx.InputMethod1" ) .withArguments() ) ηογϣϯόεͷ org.fcitx5.Fcitx5ͷ /org/freedesktop/portal/inputmethodʹܨ͙
  115. ); } void fcitx5::fcitx5_internal::poll() { auto [version] = sched::wait( gct::dbus::call<

    std::uint32_t >( input_method->callMethodAsync( "Version" ) .onInterface( "org.fcitx.Fcitx.InputMethod1" ) .withArguments() ) ); std::cout << "fcitx5 version : " << version << std::endl; std::vector< sdbus::Struct< std::string, std::string > > args{ sdbus::make_struct( std::string( "program" ), std::string( "hoge" ) ), sdbus::make_struct( std::string( "display" ), std::string( "x11:" ) ) }; auto [path,wtf] = sched::wait( gct::dbus::call< sdbus::ObjectPath, std::vector< std::uint8_t > >( input_method->callMethodAsync( "CreateInputContext" ) .onInterface( "org.fcitx.Fcitx.InputMethod1" ) .withArguments( args ) ) ); input_context = sdbus::createProxy( *conn, "org.fcitx.Fcitx5", path ); CreateInputContextͰίϯςΩετΛ࡞Δͱ ίϯςΩετͷύε͕໯͑Δ ίϯςΩετͷύεʹܨ͙
  116. thread_pool->add_co( [self=shared_from_this(),e=e,key_state=key_state]() { if( self->input_context ) { auto [result] =

    sched::wait( gct::dbus::call< bool >( self->input_context->callMethodAsync( "ProcessKeyEvent" ) .onInterface( "org.fcitx.Fcitx.InputContext1" ) .withArguments( std::uint32_t( e.sym ), std::uint32_t( e.code ), std::uint32_t( key_state ), bool( e.state == key_state::released ), std::uint32_t( e.relative_time.count() ) ) ) ); if( !result ) { std::cout << "ProcessKeyEvent failed : " << result << std::endl; } } } xkbcommonʹ௨͢લͷΩʔͷҐஔͱ ௨ͨ͠ޙͷΩʔͷจࣈΛfcitx5ʹૹΔ
  117. input_context->uponSignal( "CurrentIM" ) .onInterface( "org.fcitx.Fcitx.InputContext1" ) .call( [self=shared_from_this()]( const std::string

    &a, const std::string &b, const std::string &c ) { std::optional< promise< std::tuple< std::string, std::string, std::string > > > p; { std::scoped_lock< std::mutex > lock( self->guard ); if( self->current_im_p ) { p = std::move( *self->current_im_p ); self->current_im_p = std::nullopt; } else { self->current_im_v = std::make_tuple( a, b, c ); } } if( p ) { ม׵Τϯδϯ͕੾ΓସΘΔͱ CurrentIMγάφϧͰ ม׵Τϯδϯͷ໊લ͕ඈΜͰ͘Δ
  118. input_context->uponSignal( "CommitString" ) .onInterface( "org.fcitx.Fcitx.InputContext1" ) .call( [self=shared_from_this()]( const std::string

    &s ) { std::optional< promise< std::string > > p; { std::scoped_lock< std::mutex > lock( self->guard ); if( self->commit_p ) { p = std::move( *self->commit_p ); self->commit_p = std::nullopt; } else { self->commit_v += s; } } if( p ) { p->set_value( s ); } } ); input_context->uponSignal( "CurrentIM" ) .onInterface( "org.fcitx.Fcitx.InputContext1" ) .call( [self=shared_from_this()]( ม׵͕֬ఆ͢Δͱ CommitStringγάφϧͰ ֬ఆͨ͠಺༰͕ඈΜͰ͘Δ
  119. input_context->uponSignal( "ForwardKey" ) .onInterface( "org.fcitx.Fcitx.InputContext1" ) .call( [self=shared_from_this()]( std::uint32_t a,

    std::uint32_t b, bool c ) { std::optional< promise< std::vector< std::uint32_t > > > p; { std::scoped_lock< std::mutex > lock( self->guard ); if( self->forward_p ) { p = std::move( *self->forward_p ); self->forward_p = std::nullopt; } else { self->forward_v.push_back( a ); } } if( p ) { p->set_value( std::vector< std::uint32_t >{ a } ); } } ); input_context->uponSignal( "UpdateClientSideUI" ) ม׵Τϯδϯ͕੾ΒΕͨঢ়ଶͰ ೖྗ͞Εͨ಺༰͸ ForwardKeyγάφϧͰ ඈΜͰ͘Δ
  120. input_context->uponSignal( "UpdateClientSideUI" ) .onInterface( "org.fcitx.Fcitx.InputContext1" ) .call( [self=shared_from_this()]( const std::vector<

    sdbus::Struct< std::string, std::int32_t > > &a, std::int32_t b, const std::vector< sdbus::Struct< std::string, std::int32_t > > &c, const std::vector< sdbus::Struct< std::string, std::int32_t > > &d, const std::vector< sdbus::Struct< std::string, std::string > > &e, std::int32_t f, std::int32_t g, bool h, bool i ) { fcitx5_client_ui v; v.message = c.empty() ? std::string() : c[ 0 ].get< 0 >(); v.selected = f; v.page = h; for( auto &x: e ) { v.cands.push_back( x.get< 0 >() ); } std::optional< promise< fcitx5_client_ui > > p; ม׵ީิ΢Οϯυ΢ͷ಺༰Λߋ৽͢΂͖࣌͸ UpdateClientSideUIγάφϧ͕ඈΜͰ͘Δ
  121. input_context->uponSignal( "UpdateFormattedPreedit" ) .onInterface( "org.fcitx.Fcitx.InputContext1" ) .call( [self=shared_from_this()]( const std::vector<

    sdbus::Struct< std::string, std::int32_t > > &a, std::int32_t b ) { fcitx5_preedit v; v.selected = b; for( auto &x: a ) { v.cands.push_back( x.get< 0 >() ); } std::optional< promise< fcitx5_preedit > > p; { std::scoped_lock< std::mutex > lock( self->guard ); if( self->preedit_p ) { p = std::move( *self->preedit_p ); self->preedit_p = std::nullopt; } else { self->preedit_v = v; } Preedit(ະ֬ఆͷจࣈྻ)ͷ಺༰Λมߋ͢΂͖࣌͸ UpdateFormattedPreeditγάφϧ͕ඈΜͰ͘Δ
  122. ॎ ॻ ͖ ɹ ೔ ຊ ޠ ౳ ͷ ͍

    ͘ ͭ ͔ ͷ ݴ ޠ Ͱ ͸ ্ ͔ Β Լ ʹ ς Ω ε τ Λ ਐ Ί Δ ॻ ͖ ํ ͕ ఆ ٛ ͞ Ε ͯ ͍ Δ ɹ HarfBuzz ͸ ॎ ॻ ͖ Λ α ϙ τ ͠ ͯ ͍ Δ ͷ Ͱ ೔ ຊ ޠ ͷ จ ࣈ Λ ॎ ॻ ͖ Ͱ ฒ ΂ Δ ࣄ ࣗ ମ ͸ ೉ ͠ ͘ ͳ ͍ ɹ ͠ ͔ ͠ ɹ ॎ ॻ ͖ ͷ ς Ω ε τ ͷ த ʹ English ͷ Α ͏ ͳ ॎ ॻ ͖ Ͱ ͖ ͳ ͍ ݴ ޠ ͕ ࠞ ͟ ͯ ͖ ͨ Β ά Ϧ ϑ Λ ճ స ͞ ͤ ͳ ͚ Ε ͹ ͳ Β ͳ ͍ ɹ ॎ ॻ ͖ ͷ ς Ω ε τ ͷ த ʹ תי ִ רְבִע ʿ Īvrīt ͷ Α ͏ ͳ ӈ ͔ Β ࠨ ʹ ਐ Ή ݴ ޠ ͕ ࠞ ͟ ͯ ͖ ͨ Β Ͳ ͏ ͠ Α ͏