vulkan_test2

annotate src/vku.c @ 4:c31c4115d44a

test 2, open window, create queue, etc ...
author John Tsiombikas <nuclear@member.fsf.org>
date Fri, 22 Sep 2017 15:26:29 +0300
parents 68e1c437343f
children cec4b0e7fce8
rev   line source
nuclear@3 1 #include <stdio.h>
nuclear@3 2 #include <stdlib.h>
nuclear@3 3 #include <string.h>
nuclear@4 4 #include <stdint.h>
nuclear@3 5 #include "vku.h"
nuclear@3 6
nuclear@3 7 static const char *get_device_name(VkPhysicalDeviceType type);
nuclear@3 8 static const char *get_mem_prop_flag_string(VkMemoryPropertyFlags flags);
nuclear@3 9 static const char *get_queue_flag_string(VkQueueFlagBits flags);
nuclear@3 10 static int ver_major(uint32_t ver);
nuclear@3 11 static int ver_minor(uint32_t ver);
nuclear@3 12 static int ver_patch(uint32_t ver);
nuclear@3 13 static const char *mem_size_str(long sz);
nuclear@3 14
nuclear@3 15 VkInstance vk;
nuclear@3 16 VkDevice vkdev;
nuclear@3 17 VkQueue vkq;
nuclear@3 18
nuclear@4 19 static VkPhysicalDevice *phys_devices;
nuclear@4 20 static int sel_dev, sel_qfamily;
nuclear@4 21
nuclear@4 22 static VkExtensionProperties *vkext, *vkdevext;
nuclear@4 23 static uint32_t vkext_count, vkdevext_count;
nuclear@4 24
nuclear@4 25
nuclear@4 26 int vku_have_extension(const char *name)
nuclear@4 27 {
nuclear@4 28 int i;
nuclear@4 29
nuclear@4 30 if(!vkext) {
nuclear@4 31 vkext_count = 0;
nuclear@4 32 vkEnumerateInstanceExtensionProperties(0, &vkext_count, 0);
nuclear@4 33 if(vkext_count) {
nuclear@4 34 if(!(vkext = malloc(vkext_count * sizeof *vkext))) {
nuclear@4 35 perror("failed to allocate instance extension list");
nuclear@4 36 return 0;
nuclear@4 37 }
nuclear@4 38 vkEnumerateInstanceExtensionProperties(0, &vkext_count, vkext);
nuclear@4 39
nuclear@4 40 printf("instance extensions:\n");
nuclear@4 41 for(i=0; i<(int)vkext_count; i++) {
nuclear@4 42 printf(" %s (ver: %u)\n", vkext[i].extensionName, (unsigned int)vkext[i].specVersion);
nuclear@4 43 }
nuclear@4 44 }
nuclear@4 45 }
nuclear@4 46
nuclear@4 47 for(i=0; i<(int)vkext_count; i++) {
nuclear@4 48 if(strcmp(vkext[i].extensionName, name) == 0) {
nuclear@4 49 return 1;
nuclear@4 50 }
nuclear@4 51 }
nuclear@4 52 return 0;
nuclear@4 53 }
nuclear@4 54
nuclear@4 55 int vku_have_device_extension(const char *name)
nuclear@4 56 {
nuclear@4 57 int i;
nuclear@4 58
nuclear@4 59 if(sel_dev < 0) return 0;
nuclear@4 60
nuclear@4 61 if(!vkdevext) {
nuclear@4 62 vkdevext_count = 0;
nuclear@4 63 vkEnumerateDeviceExtensionProperties(phys_devices[sel_dev], 0, &vkdevext_count, 0);
nuclear@4 64 if(vkdevext_count) {
nuclear@4 65 if(!(vkdevext = malloc(vkdevext_count * sizeof *vkdevext))) {
nuclear@4 66 perror("failed to allocate device extension list");
nuclear@4 67 return 0;
nuclear@4 68 }
nuclear@4 69 vkEnumerateDeviceExtensionProperties(phys_devices[sel_dev], 0, &vkdevext_count, vkdevext);
nuclear@4 70
nuclear@4 71 printf("selected device extensions:\n");
nuclear@4 72 for(i=0; i<(int)vkdevext_count; i++) {
nuclear@4 73 printf(" %s (ver: %u)\n", vkdevext[i].extensionName, (unsigned int)vkdevext[i].specVersion);
nuclear@4 74 }
nuclear@4 75 }
nuclear@4 76 }
nuclear@4 77
nuclear@4 78 for(i=0; i<(int)vkdevext_count; i++) {
nuclear@4 79 if(strcmp(vkdevext[i].extensionName, name) == 0) {
nuclear@4 80 return 1;
nuclear@4 81 }
nuclear@4 82 }
nuclear@4 83 return 0;
nuclear@4 84 }
nuclear@4 85
nuclear@3 86 int vku_create_dev(void)
nuclear@3 87 {
nuclear@3 88 int i, j;
nuclear@3 89 VkInstanceCreateInfo inst_info;
nuclear@3 90 VkDeviceCreateInfo dev_info;
nuclear@3 91 VkDeviceQueueCreateInfo queue_info;
nuclear@3 92 VkCommandPoolCreateInfo cmdpool_info;
nuclear@3 93 uint32_t num_devices;
nuclear@3 94 float qprio = 0.0f;
nuclear@3 95
nuclear@4 96 static const char *ext_names[] = {
nuclear@4 97 #ifdef VK_USE_PLATFORM_XLIB_KHR
nuclear@4 98 "VK_KHR_xlib_surface",
nuclear@4 99 #endif
nuclear@4 100 "VK_KHR_surface"
nuclear@4 101 };
nuclear@4 102
nuclear@4 103 sel_dev = -1;
nuclear@4 104 sel_qfamily = -1;
nuclear@4 105
nuclear@4 106 for(i=0; i<sizeof ext_names / sizeof *ext_names; i++) {
nuclear@4 107 if(!vku_have_extension(ext_names[i])) {
nuclear@4 108 fprintf(stderr, "required extension (%s) not found\n", ext_names[i]);
nuclear@4 109 return -1;
nuclear@4 110 }
nuclear@4 111 }
nuclear@4 112
nuclear@3 113 memset(&inst_info, 0, sizeof inst_info);
nuclear@3 114 inst_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
nuclear@4 115 inst_info.ppEnabledExtensionNames = ext_names;
nuclear@4 116 inst_info.enabledExtensionCount = sizeof ext_names / sizeof *ext_names;
nuclear@3 117
nuclear@3 118 if(vkCreateInstance(&inst_info, 0, &vk) != 0) {
nuclear@3 119 fprintf(stderr, "failed to create vulkan instance\n");
nuclear@3 120 return -1;
nuclear@3 121 }
nuclear@3 122 printf("created vulkan instance\n");
nuclear@3 123
nuclear@3 124 if(vkEnumeratePhysicalDevices(vk, &num_devices, 0) != 0) {
nuclear@3 125 fprintf(stderr, "failed to enumerate vulkan physical devices\n");
nuclear@3 126 return -1;
nuclear@3 127 }
nuclear@4 128 phys_devices = malloc(num_devices * sizeof *phys_devices);
nuclear@4 129 if(vkEnumeratePhysicalDevices(vk, &num_devices, phys_devices) != 0) {
nuclear@3 130 fprintf(stderr, "failed to enumerate vulkan physical devices\n");
nuclear@3 131 return -1;
nuclear@3 132 }
nuclear@3 133 printf("found %u physical device(s)\n", (unsigned int)num_devices);
nuclear@3 134
nuclear@3 135 for(i=0; i<(int)num_devices; i++) {
nuclear@3 136 VkPhysicalDeviceProperties dev_prop;
nuclear@3 137 VkPhysicalDeviceMemoryProperties mem_prop;
nuclear@3 138 VkQueueFamilyProperties *qprop;
nuclear@3 139 uint32_t qprop_count;
nuclear@3 140
nuclear@4 141 vkGetPhysicalDeviceProperties(phys_devices[i], &dev_prop);
nuclear@3 142
nuclear@3 143 printf("Device %d: %s\n", i, dev_prop.deviceName);
nuclear@3 144 printf(" type: %s\n", get_device_name(dev_prop.deviceType));
nuclear@3 145 printf(" API version: %d.%d.%d\n", ver_major(dev_prop.apiVersion), ver_minor(dev_prop.apiVersion),
nuclear@3 146 ver_patch(dev_prop.apiVersion));
nuclear@3 147 printf(" driver version: %d.%d.%d\n", ver_major(dev_prop.driverVersion), ver_minor(dev_prop.driverVersion),
nuclear@3 148 ver_patch(dev_prop.driverVersion));
nuclear@3 149 printf(" vendor id: %x device id: %x\n", dev_prop.vendorID, dev_prop.deviceID);
nuclear@3 150
nuclear@3 151
nuclear@4 152 vkGetPhysicalDeviceMemoryProperties(phys_devices[i], &mem_prop);
nuclear@3 153 printf(" %d memory heaps:\n", mem_prop.memoryHeapCount);
nuclear@3 154 for(j=0; j<mem_prop.memoryHeapCount; j++) {
nuclear@3 155 VkMemoryHeap heap = mem_prop.memoryHeaps[j];
nuclear@3 156 printf(" Heap %d - size: %s, flags: %s\n", j, mem_size_str(heap.size),
nuclear@3 157 heap.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT ? "device-local" : "-");
nuclear@3 158 }
nuclear@3 159 printf(" %d memory types:\n", mem_prop.memoryTypeCount);
nuclear@3 160 for(j=0; j<mem_prop.memoryTypeCount; j++) {
nuclear@3 161 VkMemoryType type = mem_prop.memoryTypes[j];
nuclear@3 162 printf(" Type %d - heap: %d, flags: %s\n", j, type.heapIndex,
nuclear@3 163 get_mem_prop_flag_string(type.propertyFlags));
nuclear@3 164 }
nuclear@3 165
nuclear@4 166 vkGetPhysicalDeviceQueueFamilyProperties(phys_devices[i], &qprop_count, 0);
nuclear@3 167 if(qprop_count <= 0) {
nuclear@3 168 continue;
nuclear@3 169 }
nuclear@3 170 qprop = malloc(qprop_count * sizeof *qprop);
nuclear@4 171 vkGetPhysicalDeviceQueueFamilyProperties(phys_devices[i], &qprop_count, qprop);
nuclear@3 172
nuclear@3 173 for(j=0; j<qprop_count; j++) {
nuclear@3 174 printf(" Queue family %d:\n", j);
nuclear@3 175 printf(" flags: %s\n", get_queue_flag_string(qprop[j].queueFlags));
nuclear@3 176 printf(" num queues: %u\n", qprop[j].queueCount);
nuclear@3 177
nuclear@3 178 if(qprop[j].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
nuclear@3 179 sel_dev = i;
nuclear@3 180 sel_qfamily = j;
nuclear@3 181 }
nuclear@3 182 }
nuclear@3 183 free(qprop);
nuclear@3 184 }
nuclear@3 185
nuclear@3 186 if(sel_dev < 0 || sel_qfamily < 0) {
nuclear@3 187 fprintf(stderr, "failed to find any device with a graphics-capable command queue\n");
nuclear@3 188 vkDestroyDevice(vkdev, 0);
nuclear@3 189 return -1;
nuclear@3 190 }
nuclear@3 191
nuclear@3 192 /* create device & command queue */
nuclear@3 193 memset(&queue_info, 0, sizeof queue_info);
nuclear@3 194 queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
nuclear@3 195 queue_info.queueFamilyIndex = sel_qfamily;
nuclear@3 196 queue_info.queueCount = 1;
nuclear@3 197 queue_info.pQueuePriorities = &qprio;
nuclear@3 198
nuclear@3 199 memset(&dev_info, 0, sizeof dev_info);
nuclear@3 200 dev_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
nuclear@3 201 dev_info.queueCreateInfoCount = 1;
nuclear@3 202 dev_info.pQueueCreateInfos = &queue_info;
nuclear@3 203
nuclear@4 204 if(vkCreateDevice(phys_devices[sel_dev], &dev_info, 0, &vkdev) != 0) {
nuclear@3 205 fprintf(stderr, "failed to create device %d\n", sel_dev);
nuclear@3 206 return -1;
nuclear@3 207 }
nuclear@3 208 printf("created device %d\n", sel_dev);
nuclear@3 209
nuclear@3 210 vkGetDeviceQueue(vkdev, sel_qfamily, 0, &vkq);
nuclear@3 211
nuclear@3 212 /* create command buffer pool */
nuclear@3 213 memset(&cmdpool_info, 0, sizeof cmdpool_info);
nuclear@3 214 cmdpool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
nuclear@3 215 cmdpool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
nuclear@3 216 cmdpool_info.queueFamilyIndex = sel_qfamily;
nuclear@3 217
nuclear@3 218 if(vkCreateCommandPool(vkdev, &cmdpool_info, 0, &vkcmdpool) != 0) {
nuclear@3 219 fprintf(stderr, "failed to get command quque!\n");
nuclear@3 220 return -1;
nuclear@3 221 }
nuclear@3 222
nuclear@4 223 if(!(vkcmdbuf = vku_alloc_cmdbuf(vkcmdpool, VK_COMMAND_BUFFER_LEVEL_PRIMARY))) {
nuclear@4 224 fprintf(stderr, "failed to create primary command buffer\n");
nuclear@4 225 return -1;
nuclear@4 226 }
nuclear@4 227
nuclear@3 228 return 0;
nuclear@3 229 }
nuclear@3 230
nuclear@3 231 void vku_cleanup(void)
nuclear@3 232 {
nuclear@3 233 if(vk) {
nuclear@3 234 vkDeviceWaitIdle(vkdev);
nuclear@4 235 vkDestroyCommandPool(vkdev, vkcmdpool, 0);
nuclear@3 236 vkDestroyDevice(vkdev, 0);
nuclear@3 237 vkDestroyInstance(vk, 0);
nuclear@3 238 vk = 0;
nuclear@3 239 }
nuclear@4 240
nuclear@4 241 free(phys_devices);
nuclear@4 242 phys_devices = 0;
nuclear@3 243 }
nuclear@3 244
nuclear@4 245 VkCommandBuffer vku_alloc_cmdbuf(VkCommandPool pool, VkCommandBufferLevel level)
nuclear@3 246 {
nuclear@4 247 VkCommandBuffer cmdbuf;
nuclear@4 248 VkCommandBufferAllocateInfo inf;
nuclear@4 249
nuclear@4 250 memset(&inf, 0, sizeof inf);
nuclear@4 251 inf.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
nuclear@4 252 inf.commandPool = pool;
nuclear@4 253 inf.level = level;
nuclear@4 254 inf.commandBufferCount = 1;
nuclear@4 255
nuclear@4 256 if(vkAllocateCommandBuffers(vkdev, &inf, &cmdbuf) != 0) {
nuclear@4 257 return 0;
nuclear@4 258 }
nuclear@4 259 return cmdbuf;
nuclear@4 260 }
nuclear@4 261
nuclear@4 262 void vku_free_cmdbuf(VkCommandPool pool, VkCommandBuffer buf)
nuclear@4 263 {
nuclear@4 264 vkFreeCommandBuffers(vkdev, pool, 1, &buf);
nuclear@4 265 }
nuclear@4 266
nuclear@4 267 void vku_begin_cmdbuf(VkCommandBuffer buf, unsigned int flags)
nuclear@4 268 {
nuclear@4 269 VkCommandBufferBeginInfo inf;
nuclear@4 270
nuclear@4 271 memset(&inf, 0, sizeof inf);
nuclear@4 272 inf.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
nuclear@4 273 inf.flags = flags;
nuclear@4 274
nuclear@4 275 vkBeginCommandBuffer(buf, &inf);
nuclear@4 276 }
nuclear@4 277
nuclear@4 278
nuclear@4 279 void vku_end_cmdbuf(VkCommandBuffer buf)
nuclear@4 280 {
nuclear@4 281 vkEndCommandBuffer(buf);
nuclear@4 282 }
nuclear@4 283
nuclear@4 284 void vku_reset_cmdbuf(VkCommandBuffer buf)
nuclear@4 285 {
nuclear@4 286 vkResetCommandBuffer(buf, 0);
nuclear@4 287 }
nuclear@4 288
nuclear@4 289 void vku_submit_cmdbuf(VkQueue q, VkCommandBuffer buf, VkFence done_fence)
nuclear@4 290 {
nuclear@4 291 VkSubmitInfo info;
nuclear@4 292
nuclear@4 293 memset(&info, 0, sizeof info);
nuclear@4 294 info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
nuclear@4 295 info.commandBufferCount = 1;
nuclear@4 296 info.pCommandBuffers = &buf;
nuclear@4 297
nuclear@4 298 vkQueueSubmit(q, 1, &info, done_fence);
nuclear@4 299 }
nuclear@4 300
nuclear@4 301 struct vku_buffer *vku_create_buffer(int sz, unsigned int usage)
nuclear@4 302 {
nuclear@4 303 struct vku_buffer *buf;
nuclear@3 304 VkBufferCreateInfo binfo;
nuclear@3 305
nuclear@3 306 if(!(buf = malloc(sizeof *buf))) {
nuclear@3 307 perror("failed to allocate vk_buffer structure");
nuclear@3 308 return 0;
nuclear@3 309 }
nuclear@3 310
nuclear@3 311 memset(&binfo, 0, sizeof binfo);
nuclear@3 312 binfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
nuclear@3 313 binfo.size = sz;
nuclear@3 314 binfo.usage = usage;
nuclear@3 315 binfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
nuclear@3 316
nuclear@3 317 if(vkCreateBuffer(vkdev, &binfo, 0, &buf->buf) != 0) {
nuclear@3 318 fprintf(stderr, "failed to create %d byte buffer (usage: %x)\n", sz, usage);
nuclear@3 319 return 0;
nuclear@3 320 }
nuclear@3 321 // TODO back with memory
nuclear@3 322 return buf;
nuclear@3 323 }
nuclear@3 324
nuclear@4 325 void vku_destroy_buffer(struct vku_buffer *buf)
nuclear@3 326 {
nuclear@3 327 if(buf) {
nuclear@3 328 vkDestroyBuffer(vkdev, buf->buf, 0);
nuclear@3 329 free(buf);
nuclear@3 330 }
nuclear@3 331 }
nuclear@3 332
nuclear@4 333 void vku_cmd_copybuf(VkCommandBuffer cmdbuf, VkBuffer dest, int doffs,
nuclear@4 334 VkBuffer src, int soffs, int size)
nuclear@4 335 {
nuclear@4 336 VkBufferCopy copy;
nuclear@4 337 copy.size = size;
nuclear@4 338 copy.srcOffset = soffs;
nuclear@4 339 copy.dstOffset = doffs;
nuclear@4 340
nuclear@4 341 vkCmdCopyBuffer(cmdbuf, src, dest, 1, &copy);
nuclear@4 342 }
nuclear@4 343
nuclear@4 344 #ifdef VK_USE_PLATFORM_XLIB_KHR
nuclear@4 345 int vku_xlib_usable_visual(Display *dpy, VisualID vid)
nuclear@4 346 {
nuclear@4 347 return vkGetPhysicalDeviceXlibPresentationSupportKHR(phys_devices[sel_dev],
nuclear@4 348 sel_qfamily, dpy, vid);
nuclear@4 349 }
nuclear@4 350 #endif /* VK_USE_PLATFORM_XLIB_KHR */
nuclear@4 351
nuclear@3 352 static const char *get_device_name(VkPhysicalDeviceType type)
nuclear@3 353 {
nuclear@3 354 switch(type) {
nuclear@3 355 case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
nuclear@3 356 return "integrated GPU";
nuclear@3 357 case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
nuclear@3 358 return "discrete GPU";
nuclear@3 359 case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
nuclear@3 360 return "virtual GPU";
nuclear@3 361 case VK_PHYSICAL_DEVICE_TYPE_CPU:
nuclear@3 362 return "CPU";
nuclear@3 363 default:
nuclear@3 364 break;
nuclear@3 365 }
nuclear@3 366 return "unknown";
nuclear@3 367 }
nuclear@3 368
nuclear@3 369 static const char *get_mem_prop_flag_string(VkMemoryPropertyFlags flags)
nuclear@3 370 {
nuclear@3 371 static char str[128];
nuclear@3 372
nuclear@3 373 str[0] = 0;
nuclear@3 374 if(flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
nuclear@3 375 strcat(str, "device-local ");
nuclear@3 376 }
nuclear@3 377 if(flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
nuclear@3 378 strcat(str, "host-visible ");
nuclear@3 379 }
nuclear@3 380 if(flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) {
nuclear@3 381 strcat(str, "host-coherent ");
nuclear@3 382 }
nuclear@3 383 if(flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) {
nuclear@3 384 strcat(str, "host-cached ");
nuclear@3 385 }
nuclear@3 386 if(flags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
nuclear@3 387 strcat(str, "lazily-allocated ");
nuclear@3 388 }
nuclear@3 389
nuclear@3 390 if(!*str) {
nuclear@3 391 strcat(str, "-");
nuclear@3 392 }
nuclear@3 393 return str;
nuclear@3 394 }
nuclear@3 395
nuclear@3 396 static const char *get_queue_flag_string(VkQueueFlagBits flags)
nuclear@3 397 {
nuclear@3 398 static char str[128];
nuclear@3 399
nuclear@3 400 str[0] = 0;
nuclear@3 401 if(flags & VK_QUEUE_GRAPHICS_BIT) {
nuclear@3 402 strcat(str, "graphics ");
nuclear@3 403 }
nuclear@3 404 if(flags & VK_QUEUE_COMPUTE_BIT) {
nuclear@3 405 strcat(str, "compute ");
nuclear@3 406 }
nuclear@3 407 if(flags & VK_QUEUE_TRANSFER_BIT) {
nuclear@3 408 strcat(str, "transfer ");
nuclear@3 409 }
nuclear@3 410 if(flags & VK_QUEUE_SPARSE_BINDING_BIT) {
nuclear@3 411 strcat(str, "sparse-binding ");
nuclear@3 412 }
nuclear@3 413 if(!*str) {
nuclear@3 414 strcat(str, "-");
nuclear@3 415 }
nuclear@3 416 return str;
nuclear@3 417 }
nuclear@3 418
nuclear@3 419 static int ver_major(uint32_t ver)
nuclear@3 420 {
nuclear@3 421 return (ver >> 22) & 0x3ff;
nuclear@3 422 }
nuclear@3 423
nuclear@3 424 static int ver_minor(uint32_t ver)
nuclear@3 425 {
nuclear@3 426 return (ver >> 12) & 0x3ff;
nuclear@3 427 }
nuclear@3 428
nuclear@3 429 static int ver_patch(uint32_t ver)
nuclear@3 430 {
nuclear@3 431 return ver & 0xfff;
nuclear@3 432 }
nuclear@3 433
nuclear@3 434 static const char *mem_size_str(long sz)
nuclear@3 435 {
nuclear@3 436 static char str[64];
nuclear@3 437 static const char *unitstr[] = { "bytes", "KB", "MB", "GB", "TB", "PB", 0 };
nuclear@3 438 int uidx = 0;
nuclear@3 439 sz *= 10;
nuclear@3 440
nuclear@3 441 while(sz >= 10240 && unitstr[uidx + 1]) {
nuclear@3 442 sz /= 1024;
nuclear@3 443 ++uidx;
nuclear@3 444 }
nuclear@3 445 sprintf(str, "%ld.%ld %s", sz / 10, sz % 10, unitstr[uidx]);
nuclear@3 446 return str;
nuclear@3 447 }