1 /*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "../SDL_internal.h"
22
23 #include "SDL_vulkan_internal.h"
24 #include "SDL_error.h"
25
26 #if SDL_VIDEO_VULKAN
27
SDL_Vulkan_GetResultString(VkResult result)28 const char *SDL_Vulkan_GetResultString(VkResult result)
29 {
30 switch ((int)result) {
31 case VK_SUCCESS:
32 return "VK_SUCCESS";
33 case VK_NOT_READY:
34 return "VK_NOT_READY";
35 case VK_TIMEOUT:
36 return "VK_TIMEOUT";
37 case VK_EVENT_SET:
38 return "VK_EVENT_SET";
39 case VK_EVENT_RESET:
40 return "VK_EVENT_RESET";
41 case VK_INCOMPLETE:
42 return "VK_INCOMPLETE";
43 case VK_ERROR_OUT_OF_HOST_MEMORY:
44 return "VK_ERROR_OUT_OF_HOST_MEMORY";
45 case VK_ERROR_OUT_OF_DEVICE_MEMORY:
46 return "VK_ERROR_OUT_OF_DEVICE_MEMORY";
47 case VK_ERROR_INITIALIZATION_FAILED:
48 return "VK_ERROR_INITIALIZATION_FAILED";
49 case VK_ERROR_DEVICE_LOST:
50 return "VK_ERROR_DEVICE_LOST";
51 case VK_ERROR_MEMORY_MAP_FAILED:
52 return "VK_ERROR_MEMORY_MAP_FAILED";
53 case VK_ERROR_LAYER_NOT_PRESENT:
54 return "VK_ERROR_LAYER_NOT_PRESENT";
55 case VK_ERROR_EXTENSION_NOT_PRESENT:
56 return "VK_ERROR_EXTENSION_NOT_PRESENT";
57 case VK_ERROR_FEATURE_NOT_PRESENT:
58 return "VK_ERROR_FEATURE_NOT_PRESENT";
59 case VK_ERROR_INCOMPATIBLE_DRIVER:
60 return "VK_ERROR_INCOMPATIBLE_DRIVER";
61 case VK_ERROR_TOO_MANY_OBJECTS:
62 return "VK_ERROR_TOO_MANY_OBJECTS";
63 case VK_ERROR_FORMAT_NOT_SUPPORTED:
64 return "VK_ERROR_FORMAT_NOT_SUPPORTED";
65 case VK_ERROR_FRAGMENTED_POOL:
66 return "VK_ERROR_FRAGMENTED_POOL";
67 case VK_ERROR_UNKNOWN:
68 return "VK_ERROR_UNKNOWN";
69 case VK_ERROR_OUT_OF_POOL_MEMORY:
70 return "VK_ERROR_OUT_OF_POOL_MEMORY";
71 case VK_ERROR_INVALID_EXTERNAL_HANDLE:
72 return "VK_ERROR_INVALID_EXTERNAL_HANDLE";
73 case VK_ERROR_FRAGMENTATION:
74 return "VK_ERROR_FRAGMENTATION";
75 case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS:
76 return "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS";
77 case VK_ERROR_SURFACE_LOST_KHR:
78 return "VK_ERROR_SURFACE_LOST_KHR";
79 case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:
80 return "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR";
81 case VK_SUBOPTIMAL_KHR:
82 return "VK_SUBOPTIMAL_KHR";
83 case VK_ERROR_OUT_OF_DATE_KHR:
84 return "VK_ERROR_OUT_OF_DATE_KHR";
85 case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR:
86 return "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR";
87 case VK_ERROR_VALIDATION_FAILED_EXT:
88 return "VK_ERROR_VALIDATION_FAILED_EXT";
89 case VK_ERROR_INVALID_SHADER_NV:
90 return "VK_ERROR_INVALID_SHADER_NV";
91 #if VK_HEADER_VERSION >= 135 && VK_HEADER_VERSION < 162
92 case VK_ERROR_INCOMPATIBLE_VERSION_KHR:
93 return "VK_ERROR_INCOMPATIBLE_VERSION_KHR";
94 #endif
95 case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT:
96 return "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT";
97 case VK_ERROR_NOT_PERMITTED_EXT:
98 return "VK_ERROR_NOT_PERMITTED_EXT";
99 case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
100 return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT";
101 case VK_THREAD_IDLE_KHR:
102 return "VK_THREAD_IDLE_KHR";
103 case VK_THREAD_DONE_KHR:
104 return "VK_THREAD_DONE_KHR";
105 case VK_OPERATION_DEFERRED_KHR:
106 return "VK_OPERATION_DEFERRED_KHR";
107 case VK_OPERATION_NOT_DEFERRED_KHR:
108 return "VK_OPERATION_NOT_DEFERRED_KHR";
109 case VK_PIPELINE_COMPILE_REQUIRED_EXT:
110 return "VK_PIPELINE_COMPILE_REQUIRED_EXT";
111 default:
112 break;
113 }
114 if (result < 0) {
115 return "VK_ERROR_<Unknown>";
116 }
117 return "VK_<Unknown>";
118 }
119
SDL_Vulkan_CreateInstanceExtensionsList(PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties,Uint32 * extensionCount)120 VkExtensionProperties *SDL_Vulkan_CreateInstanceExtensionsList(
121 PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties,
122 Uint32 *extensionCount)
123 {
124 Uint32 count = 0;
125 VkResult result = vkEnumerateInstanceExtensionProperties(NULL, &count, NULL);
126 VkExtensionProperties *retval;
127
128 if (result == VK_ERROR_INCOMPATIBLE_DRIVER) {
129 /* Avoid the ERR_MAX_STRLEN limit by passing part of the message as a string argument. */
130 SDL_SetError(
131 "You probably don't have a working Vulkan driver installed. %s %s %s(%d)",
132 "Getting Vulkan extensions failed:",
133 "vkEnumerateInstanceExtensionProperties returned",
134 SDL_Vulkan_GetResultString(result),
135 (int)result);
136 return NULL;
137 } else if (result != VK_SUCCESS) {
138 SDL_SetError(
139 "Getting Vulkan extensions failed: vkEnumerateInstanceExtensionProperties returned "
140 "%s(%d)",
141 SDL_Vulkan_GetResultString(result),
142 (int)result);
143 return NULL;
144 }
145
146 if (count == 0) {
147 retval = SDL_calloc(1, sizeof(VkExtensionProperties)); // so we can return non-null
148 } else {
149 retval = SDL_calloc(count, sizeof(VkExtensionProperties));
150 }
151
152 if (!retval) {
153 SDL_OutOfMemory();
154 return NULL;
155 }
156
157 result = vkEnumerateInstanceExtensionProperties(NULL, &count, retval);
158 if (result != VK_SUCCESS) {
159 SDL_SetError(
160 "Getting Vulkan extensions failed: vkEnumerateInstanceExtensionProperties returned "
161 "%s(%d)",
162 SDL_Vulkan_GetResultString(result),
163 (int)result);
164 SDL_free(retval);
165 return NULL;
166 }
167 *extensionCount = count;
168 return retval;
169 }
170
SDL_Vulkan_GetInstanceExtensions_Helper(unsigned * userCount,const char ** userNames,unsigned nameCount,const char * const * names)171 SDL_bool SDL_Vulkan_GetInstanceExtensions_Helper(unsigned *userCount,
172 const char **userNames,
173 unsigned nameCount,
174 const char *const *names)
175 {
176 if (userNames) {
177 unsigned i;
178
179 if (*userCount < nameCount) {
180 SDL_SetError("Output array for SDL_Vulkan_GetInstanceExtensions needs to be at least %d big", nameCount);
181 return SDL_FALSE;
182 }
183
184 for (i = 0; i < nameCount; i++) {
185 userNames[i] = names[i];
186 }
187 }
188 *userCount = nameCount;
189 return SDL_TRUE;
190 }
191
192 /* Alpha modes, in order of preference */
193 static const VkDisplayPlaneAlphaFlagBitsKHR alphaModes[4] = {
194 VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR,
195 VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR,
196 VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR,
197 VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_PREMULTIPLIED_BIT_KHR,
198 };
199
SDL_Vulkan_Display_CreateSurface(void * vkGetInstanceProcAddr_,VkInstance instance,VkSurfaceKHR * surface)200 SDL_bool SDL_Vulkan_Display_CreateSurface(void *vkGetInstanceProcAddr_,
201 VkInstance instance,
202 VkSurfaceKHR *surface)
203 {
204 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr =
205 (PFN_vkGetInstanceProcAddr)vkGetInstanceProcAddr_;
206 #define VULKAN_INSTANCE_FUNCTION(name) \
207 PFN_##name name = (PFN_##name)vkGetInstanceProcAddr((VkInstance)instance, #name)
208 VULKAN_INSTANCE_FUNCTION(vkEnumeratePhysicalDevices);
209 VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceDisplayPropertiesKHR);
210 VULKAN_INSTANCE_FUNCTION(vkGetDisplayModePropertiesKHR);
211 VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceDisplayPlanePropertiesKHR);
212 VULKAN_INSTANCE_FUNCTION(vkGetDisplayPlaneCapabilitiesKHR);
213 VULKAN_INSTANCE_FUNCTION(vkGetDisplayPlaneSupportedDisplaysKHR);
214 VULKAN_INSTANCE_FUNCTION(vkCreateDisplayPlaneSurfaceKHR);
215 #undef VULKAN_INSTANCE_FUNCTION
216 VkDisplaySurfaceCreateInfoKHR createInfo;
217 VkResult result;
218 uint32_t physicalDeviceCount = 0;
219 VkPhysicalDevice *physicalDevices = NULL;
220 uint32_t physicalDeviceIndex;
221 const char *chosenDisplayId;
222 int displayId = 0; /* Counting from physical device 0, display 0 */
223
224 if (!vkEnumeratePhysicalDevices ||
225 !vkGetPhysicalDeviceDisplayPropertiesKHR ||
226 !vkGetDisplayModePropertiesKHR ||
227 !vkGetPhysicalDeviceDisplayPlanePropertiesKHR ||
228 !vkGetDisplayPlaneCapabilitiesKHR ||
229 !vkGetDisplayPlaneSupportedDisplaysKHR ||
230 !vkCreateDisplayPlaneSurfaceKHR) {
231 SDL_SetError(VK_KHR_DISPLAY_EXTENSION_NAME " extension is not enabled in the Vulkan instance.");
232 goto error;
233 }
234
235 if ((chosenDisplayId = SDL_getenv("SDL_VULKAN_DISPLAY")) != NULL) {
236 displayId = SDL_atoi(chosenDisplayId);
237 }
238
239 /* Enumerate physical devices */
240 result = vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, NULL);
241 if (result != VK_SUCCESS) {
242 SDL_SetError("Could not enumerate Vulkan physical devices");
243 goto error;
244 }
245
246 if (physicalDeviceCount == 0) {
247 SDL_SetError("No Vulkan physical devices");
248 goto error;
249 }
250
251 physicalDevices = SDL_malloc(sizeof(VkPhysicalDevice) * physicalDeviceCount);
252 if (!physicalDevices) {
253 SDL_OutOfMemory();
254 goto error;
255 }
256
257 result = vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, physicalDevices);
258 if (result != VK_SUCCESS) {
259 SDL_SetError("Error enumerating physical devices");
260 goto error;
261 }
262
263 for (physicalDeviceIndex = 0; physicalDeviceIndex < physicalDeviceCount; physicalDeviceIndex++) {
264 VkPhysicalDevice physicalDevice = physicalDevices[physicalDeviceIndex];
265 uint32_t displayPropertiesCount = 0;
266 VkDisplayPropertiesKHR *displayProperties = NULL;
267 uint32_t displayModePropertiesCount = 0;
268 VkDisplayModePropertiesKHR *displayModeProperties = NULL;
269 int bestMatchIndex = -1;
270 uint32_t refreshRate = 0;
271 uint32_t i;
272 uint32_t displayPlanePropertiesCount = 0;
273 int planeIndex = -1;
274 VkDisplayKHR display;
275 VkDisplayPlanePropertiesKHR *displayPlaneProperties = NULL;
276 VkExtent2D extent;
277 VkDisplayPlaneCapabilitiesKHR planeCaps;
278
279 /* Get information about the physical displays */
280 result = vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayPropertiesCount, NULL);
281 if (result != VK_SUCCESS || displayPropertiesCount == 0) {
282 /* This device has no physical device display properties, move on to next. */
283 continue;
284 }
285 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Number of display properties for device %u: %u",
286 physicalDeviceIndex, displayPropertiesCount);
287
288 if (displayId < 0 || (uint32_t) displayId >= displayPropertiesCount) {
289 /* Display id specified was higher than number of available displays, move to next physical device. */
290 displayId -= displayPropertiesCount;
291 continue;
292 }
293
294 displayProperties = SDL_malloc(sizeof(VkDisplayPropertiesKHR) * displayPropertiesCount);
295 if (!displayProperties) {
296 SDL_OutOfMemory();
297 goto error;
298 }
299
300 result = vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayPropertiesCount, displayProperties);
301 if (result != VK_SUCCESS || displayPropertiesCount == 0) {
302 SDL_free(displayProperties);
303 SDL_SetError("Error enumerating physical device displays");
304 goto error;
305 }
306
307 display = displayProperties[displayId].display;
308 extent = displayProperties[displayId].physicalResolution;
309 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Display: %s Native resolution: %ux%u",
310 displayProperties[displayId].displayName, extent.width, extent.height);
311
312 SDL_free(displayProperties);
313 displayProperties = NULL;
314
315 /* Get display mode properties for the chosen display */
316 result = vkGetDisplayModePropertiesKHR(physicalDevice, display, &displayModePropertiesCount, NULL);
317 if (result != VK_SUCCESS || displayModePropertiesCount == 0)
318 {
319 SDL_SetError("Error enumerating display modes");
320 goto error;
321 }
322 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Number of display modes: %u", displayModePropertiesCount);
323
324 displayModeProperties = SDL_malloc(sizeof(VkDisplayModePropertiesKHR) * displayModePropertiesCount);
325 if (!displayModeProperties) {
326 SDL_OutOfMemory();
327 goto error;
328 }
329
330 result = vkGetDisplayModePropertiesKHR(physicalDevice, display, &displayModePropertiesCount, displayModeProperties);
331 if (result != VK_SUCCESS || displayModePropertiesCount == 0) {
332 SDL_SetError("Error enumerating display modes");
333 SDL_free(displayModeProperties);
334 goto error;
335 }
336
337 /* Try to find a display mode that matches the native resolution */
338 for (i = 0; i < displayModePropertiesCount; ++i) {
339 if (displayModeProperties[i].parameters.visibleRegion.width == extent.width &&
340 displayModeProperties[i].parameters.visibleRegion.height == extent.height &&
341 displayModeProperties[i].parameters.refreshRate > refreshRate) {
342 bestMatchIndex = i;
343 refreshRate = displayModeProperties[i].parameters.refreshRate;
344 }
345 }
346
347 if (bestMatchIndex < 0) {
348 SDL_SetError("Found no matching display mode");
349 SDL_free(displayModeProperties);
350 goto error;
351 }
352
353 SDL_zero(createInfo);
354 createInfo.displayMode = displayModeProperties[bestMatchIndex].displayMode;
355 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Matching mode %ux%u with refresh rate %u",
356 displayModeProperties[bestMatchIndex].parameters.visibleRegion.width,
357 displayModeProperties[bestMatchIndex].parameters.visibleRegion.height,
358 refreshRate);
359
360 SDL_free(displayModeProperties);
361 displayModeProperties = NULL;
362
363 /* Try to find a plane index that supports our display */
364 result = vkGetPhysicalDeviceDisplayPlanePropertiesKHR(physicalDevice, &displayPlanePropertiesCount, NULL);
365 if (result != VK_SUCCESS || displayPlanePropertiesCount == 0) {
366 SDL_SetError("Error enumerating display planes");
367 goto error;
368 }
369 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Number of display planes: %u", displayPlanePropertiesCount);
370
371 displayPlaneProperties = SDL_malloc(sizeof(VkDisplayPlanePropertiesKHR) * displayPlanePropertiesCount);
372 if (!displayPlaneProperties) {
373 SDL_OutOfMemory();
374 goto error;
375 }
376
377 result = vkGetPhysicalDeviceDisplayPlanePropertiesKHR(physicalDevice, &displayPlanePropertiesCount, displayPlaneProperties);
378 if (result != VK_SUCCESS || displayPlanePropertiesCount == 0) {
379 SDL_SetError("Error enumerating display plane properties");
380 SDL_free(displayPlaneProperties);
381 goto error;
382 }
383
384 for (i = 0; i < displayPlanePropertiesCount; ++i) {
385 uint32_t planeSupportedDisplaysCount = 0;
386 VkDisplayKHR *planeSupportedDisplays = NULL;
387 uint32_t j;
388
389 /* Check if plane is attached to a display, if not, continue. */
390 if (displayPlaneProperties[i].currentDisplay == VK_NULL_HANDLE) {
391 continue;
392 }
393
394 /* Check supported displays for this plane. */
395 result = vkGetDisplayPlaneSupportedDisplaysKHR(physicalDevice, i, &planeSupportedDisplaysCount, NULL);
396 if (result != VK_SUCCESS || planeSupportedDisplaysCount == 0) {
397 continue; /* No supported displays, on to next plane. */
398 }
399
400 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Number of supported displays for plane %u: %u", i, planeSupportedDisplaysCount);
401
402 planeSupportedDisplays = SDL_malloc(sizeof(VkDisplayKHR) * planeSupportedDisplaysCount);
403 if (!planeSupportedDisplays) {
404 SDL_free(displayPlaneProperties);
405 SDL_OutOfMemory();
406 goto error;
407 }
408
409 result = vkGetDisplayPlaneSupportedDisplaysKHR(physicalDevice, i, &planeSupportedDisplaysCount, planeSupportedDisplays);
410 if (result != VK_SUCCESS || planeSupportedDisplaysCount == 0) {
411 SDL_SetError("Error enumerating supported displays, or no supported displays");
412 SDL_free(planeSupportedDisplays);
413 SDL_free(displayPlaneProperties);
414 goto error;
415 }
416
417 for (j = 0; j < planeSupportedDisplaysCount && planeSupportedDisplays[j] != display; ++j)
418 {
419 }
420
421 SDL_free(planeSupportedDisplays);
422 planeSupportedDisplays = NULL;
423
424 if (j == planeSupportedDisplaysCount) {
425 /* This display is not supported for this plane, move on. */
426 continue;
427 }
428
429 result = vkGetDisplayPlaneCapabilitiesKHR(physicalDevice, createInfo.displayMode, i, &planeCaps);
430 if (result != VK_SUCCESS) {
431 SDL_SetError("Error getting display plane capabilities");
432 SDL_free(displayPlaneProperties);
433 goto error;
434 }
435
436 /* Check if plane fulfills extent requirements. */
437 if (extent.width >= planeCaps.minDstExtent.width && extent.height >= planeCaps.minDstExtent.height &&
438 extent.width <= planeCaps.maxDstExtent.width && extent.height <= planeCaps.maxDstExtent.height) {
439 /* If it does, choose this plane. */
440 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Choosing plane %d, minimum extent %dx%d maximum extent %dx%d", i,
441 planeCaps.minDstExtent.width, planeCaps.minDstExtent.height,
442 planeCaps.maxDstExtent.width, planeCaps.maxDstExtent.height);
443 planeIndex = i;
444 break;
445 }
446 }
447
448 if (planeIndex < 0) {
449 SDL_SetError("No plane supports the selected resolution");
450 SDL_free(displayPlaneProperties);
451 goto error;
452 }
453
454 createInfo.planeIndex = planeIndex;
455 createInfo.planeStackIndex = displayPlaneProperties[planeIndex].currentStackIndex;
456 SDL_free(displayPlaneProperties);
457 displayPlaneProperties = NULL;
458
459 /* Find a supported alpha mode. Not all planes support OPAQUE */
460 createInfo.alphaMode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR;
461 for (i = 0; i < SDL_arraysize(alphaModes); i++) {
462 if (planeCaps.supportedAlpha & alphaModes[i]) {
463 createInfo.alphaMode = alphaModes[i];
464 break;
465 }
466 }
467 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Chose alpha mode 0x%x", createInfo.alphaMode);
468
469 /* Found a match, finally! Fill in extent, and break from loop */
470 createInfo.imageExtent = extent;
471 break;
472 }
473
474 SDL_free(physicalDevices);
475 physicalDevices = NULL;
476
477 if (physicalDeviceIndex == physicalDeviceCount) {
478 SDL_SetError("No usable displays found or requested display out of range");
479 return SDL_FALSE;
480 }
481
482 createInfo.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR;
483 createInfo.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
484 createInfo.globalAlpha = 1.0f;
485
486 result = vkCreateDisplayPlaneSurfaceKHR(instance, &createInfo, NULL, surface);
487 if (result != VK_SUCCESS) {
488 SDL_SetError("vkCreateDisplayPlaneSurfaceKHR failed: %s", SDL_Vulkan_GetResultString(result));
489 return SDL_FALSE;
490 }
491 SDL_LogDebug(SDL_LOG_CATEGORY_VIDEO, "vulkandisplay: Created surface");
492 return SDL_TRUE;
493
494 error:
495 SDL_free(physicalDevices);
496 return SDL_FALSE;
497 }
498
499 #endif
500
501 /* vi: set ts=4 sw=4 expandtab: */
502