Jump to content

Getting Started with Vulkan

Josh

516 views

The latest design of my OpenGL renderer using bindless textures has some problems, and although these can be resolved, I think I have hit the limit on how useful an initial OpenGL implementation will be for the new engine. I decided it was time to dive into the Vulkan API. This is sort of scary, because I feel like it sets me back quite a lot, but at the same time the work I do with this will carry forward much better. A Vulkan-based renderer can run on Windows, Linux, Mac, iOS, Android, PS4, and Nintendo Switch.

So far my impressions of the API are pretty good. Although it is very verbose, it gives you a lot of control over things that were previously undefined or vendor-specific hacks. Below is code that initializes Vulkan and chooses a rendering device, with a preference for discrete GPUs over integrated graphics.

VkInstance inst;
VkResult res;
VkDevice device;

VkApplicationInfo appInfo = {};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "MyGame";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "TurboEngine";
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = VK_API_VERSION_1_0;

// Get extensions
uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, availableExtensions.data());
std::vector<const char*> extensions;

VkInstanceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;
createInfo.enabledExtensionCount = (uint32_t)extensions.size();
createInfo.ppEnabledExtensionNames = extensions.data();

#ifdef DEBUG
createInfo.enabledLayerCount = 1;
const char* DEBUG_LAYER = "VK_LAYER_LUNARG_standard_validation";
createInfo.ppEnabledLayerNames = &DEBUG_LAYER;
#endif

res = vkCreateInstance(&createInfo, NULL, &inst);
if (res == VK_ERROR_INCOMPATIBLE_DRIVER)
{
	std::cout << "cannot find a compatible Vulkan ICD\n";
	exit(-1);
}
else if (res)
{
	std::cout << "unknown error\n";
	exit(-1);
}

//Enumerate devices
uint32_t gpu_count = 1;
std::vector<VkPhysicalDevice> devices;
res = vkEnumeratePhysicalDevices(inst, &gpu_count, NULL);
if (gpu_count > 0)
{			
	devices.resize(gpu_count);
	res = vkEnumeratePhysicalDevices(inst, &gpu_count, &devices[0]);
	assert(!res && gpu_count >= 1);
}

//Sort list with discrete GPUs at the beginning
std::vector<VkPhysicalDevice> sorteddevices;
for (int n = 0; n < devices.size(); n++)
{
	VkPhysicalDeviceProperties deviceprops = VkPhysicalDeviceProperties{};
	vkGetPhysicalDeviceProperties(devices[n], &deviceprops);
	if (deviceprops.deviceType == VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
	{
		sorteddevices.insert(sorteddevices.begin(),devices[n]);
	}
	else
	{
		sorteddevices.push_back(devices[n]);
	}
}
devices = sorteddevices;

VkDeviceQueueCreateInfo queue_info = {};
unsigned int queue_family_count;

for (int n = 0; n < devices.size(); ++n)
{
	vkGetPhysicalDeviceQueueFamilyProperties(devices[n], &queue_family_count, NULL);
	if (queue_family_count >= 1)
	{
		std::vector<VkQueueFamilyProperties> queue_props;
		queue_props.resize(queue_family_count);
		vkGetPhysicalDeviceQueueFamilyProperties(devices[n], &queue_family_count, queue_props.data());

		if (queue_family_count >= 1)
		{
			bool found = false;
			for (int i = 0; i < queue_family_count; i++)
			{
				if (queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
				{
					queue_info.queueFamilyIndex = i;
					found = true;
					break;
				}
			}
			if (!found) continue;

			float queue_priorities[1] = { 0.0 };
			queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
			queue_info.pNext = NULL;
			queue_info.queueCount = 1;
			queue_info.pQueuePriorities = queue_priorities;

			VkDeviceCreateInfo device_info = {};
			device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
			device_info.pNext = NULL;
			device_info.queueCreateInfoCount = 1;
			device_info.pQueueCreateInfos = &queue_info;
			device_info.enabledExtensionCount = 0;
			device_info.ppEnabledExtensionNames = NULL;
			device_info.enabledLayerCount = 0;
			device_info.ppEnabledLayerNames = NULL;
			device_info.pEnabledFeatures = NULL;

			res = vkCreateDevice(devices[n], &device_info, NULL, &device);
			if (res == VK_SUCCESS)
			{
				VkPhysicalDeviceProperties deviceprops = VkPhysicalDeviceProperties{};
				vkGetPhysicalDeviceProperties(devices[n], &deviceprops);
						
				std::cout << deviceprops.deviceName;

				vkDestroyDevice(device, NULL);
				break;
			}
		}
	}
}

vkDestroyInstance(inst, NULL);

 

  • Like 3


9 Comments


Recommended Comments

Do you already know if it will be faster than OpenGL and if so, by how much?  Or is that subject to testing?

Share this comment


Link to comment
5 hours ago, gamecreator said:

Do you already know if it will be faster than OpenGL and if so, by how much?  Or is that subject to testing?

Our new engine makes speed gains in a different way and I don't think Vulkan will make anything faster at all, honestly, over what the engine could do with OpenGL 4.3. We already have zero driver overhead, but Vulkan is more widely supported and the brand is synonymous with speed in the customer's mind. I know this because when I talk to people in person the first thing they say is "does it use Vulkan?"

It will be a lot faster than Leadwerks 4 but so was the OpenGL implementation.

Using Vulkan does not automatically make anything faster. No one will listen to me if I try to argue with that, which is fine, because I am just going to let everyone else mess up and I will make the fastest engine.

  • Confused 1

Share this comment


Link to comment

The best wishes in this project will surely be very pleasant results for users like us who are not real programmers.  :)

Share this comment


Link to comment

You're a real programmer, I just have more experience writing renderers.

Share this comment


Link to comment

No, I don't consider myself a real programmer, that is to say the magic is done by the engine, I have no idea how to create a light, I have no idea how that light casts shadows on a static or dynamic mesh, I think that in this era of globalization we are made to believe that we are programmers when we use powerful tools such as Leadwerks, where the greatest work is already done, on the other hand we are users, creators of possible games, but the programmer in all this riddle is really you, the one who has the necessary tools for people like us to think we are programmers and create something fun.  


Translated with www.DeepL.com/Translator

Share this comment


Link to comment
1 hour ago, Josh said:

Using Vulkan does not automatically make anything faster.

This could be true.  I did some searches and one response said that it depends on if you can put it to use for your engine or game.  Wiki says something similar:

Quote

Vulkan is intended to offer higher performance and more balanced CPU/GPU usage ... Vulkan is said to induce anywhere from a marginal to polynomial speedup in run time relative to other APIs if implemented properly on the same hardware

 

Share this comment


Link to comment

This is really complicated stuff, for really no good reason. I'm 600 lines into it and still can't even make a blue screen. :blink:

  • Confused 1

Share this comment


Link to comment

That confirms my suspicions, you are a programmer, I play to believe that I am, I know that you will succeed. 

Share this comment


Link to comment

Two and a half months later, we have shadow maps working:

 

  • Like 2

Share this comment


Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Blog Entries

    • By Josh in Josh's Dev Blog 5
      I did a little experiment with FPS Creator Pack #75 by upsampling the images with Gigapixel, which uses deep learning to upsample images and infer details that don't appear in the original pixels. The AI neural network does a pretty impressive job, generating results that are look better than a simple sharpen filter: I doubled the size of the textures to 1024x1024. Then I generated normal maps from the high-res images using AMD's TGA to DOT3 tool, and saved the normal maps with BC5 DDS compression. The diffuse textures were saved with BC7 DDS compression. The images below are using a 4x magnification to demonstrate the difference.


      As you can see, the image that is upsampled with deep learning looks normal and the resized image looks like there is butter on the lens! It's hard to believe the above images came from a 256x128 section of an image.
      The workflow was pretty tedious, as I had to convert images to TGA, then to uncompressed or BC5 DDS, and then to BC7 in Visual Studio. Each BC7 texture took maybe 5-10 minutes to compress! So while this set represents the optimum quality for 2019 game tech, and the format for assets we want to use in LE5, the workflow has a lot of room for improvement.
      You can download the full package here:
      FPSCPack75TexturesHD.zip
    • By Josh in Josh's Dev Blog 0
      A new beta is available with the following changes:
      Script prefixes are now changed to lowercase entity:Update(), entity:Start(), etc., as well as widget:Draw(), etc. This is because Entity() and Widget() are going to be functions to cast an object to that type. Sprites are now created on a sprite layer object. A sprite layer is created in a world and added to a camera. This allows control over what camera sees what set of sprites. See the examples for details. GUI system is partially working. Resizing the window won't reposition items correctly. Only four widget types are supported, panel, button, hyperlink, and label. Example in the FPSGame demo. The game menu system is packed into an entity script and works really nicely. Widget scripts are a little harder to develop now since they use a system of persistent objects, but performance is very much better than LE4. An interesting detail is that you get free interpolation of position and color at a rate of 60 hz. A lot of work was done to improve the Lua binding system. See details here. Error reporting and handling is much improved. No work was done on sound. No work has been done to the multicam demo, which some people had trouble with. Actors crashing / Lua stack error bug fixed. Changed .bat files to use release version of EXE instead of debug. New commands EmitEvent() and GetEvent(). If the returned event type is EVENT_NONE, there is no event. EVENT_QUIT can be emitted anywhere in the program to signal the main loop to exit. Typical usage is like this: while window:Closed() == false do while true do local event = GetEvent() if event.id == EVENT_QUIT then return end if event.id == EVENT_NONE then break end if event.id == EVENT_WINDOW_CLOSE and Window(event.source) == window then return end--you don't need this when Window:Closed() is being checked for already end world:Update() world:Render(framebuffer) end  
    • By Josh in Josh's Dev Blog 2
      DPI scaling and the 2D drawing and GUI system were an issue I was a bit concerned about, but I think I have it worked out. This all goes back to the multi-monitor support that I designed back in September. Part of that system allows you to retrieve the DPI scale for each display. This gives you another piece of information in addition to the raw screen resolution. The display scale gives you a percentage value the user expects to see vector graphics at, with 100% being what you would expect with a regular HD monitor. If we scale our GUI elements and font sizes by the display scale we can adjust for screens with any pixel density.
      This shot shows 1920x1080 fullscreen with DPI scaling set to 100%:

      Here we see the same resolution, with scaling set to 125%:

      And this is with scaling set to 150%:

      The effect of this is that if the player is using a 4K, 8K, or any other type of monitor, your game can display finely detailed text at the correct size the user expects to see. It also means that user interfaces can be rendered at any resolution for VR.
      Rather than trying to automatically scale GUI elements I am giving you full control over the raw pixels. That means you have to decide how your widgets will be scaled yourself, and program it into the game interface, but there is nothing hidden from the developer. Here is my code I am working with now to create a simple game menu. Also notice there is no CreatePanel(), CreateButton(), etc. anymore, there is just one widget you create and set the script for. I might add an option for C++ actors as well, but since these are operating on the main logic thread there's not really a downside to running the code in Lua.
      local window = ActiveWindow() if window == nullptr then return end local framebuffer = window:GetFramebuffer() if framebuffer == nil then return end self.gui = CreateGUI(self.guispritelayer) --Main background panel self.mainpanel = CreateWidget(self.gui,"",0,0,framebuffer.size.x,framebuffer.size.y) self.mainpanel:SetScript("Scripts/GUI/Panel.lua", true) local scale = window.display.scale.y local w = 120 local h = 24 local sep = 36 local x = framebuffer.size.x / 6 local y = framebuffer.size.y / 2 - sep * 3 self.resumebutton = CreateWidget(self.mainpanel,"RESUME GAME",x,y,w,h) self.resumebutton:SetScript("Scripts/GUI/Hyperlink.lua", true) self.resumebutton:SetFontSize(14 * window.display.scale.y) y=y+sep*2 self.label2 = CreateWidget(self.mainpanel,"OPTIONS",x,y,w,h) self.label2:SetScript("Scripts/GUI/Hyperlink.lua", true) self.label2:SetFontSize(14 * window.display.scale.y) y=y+sep*2 self.quitbutton = CreateWidget(self.mainpanel,"QUIT", x,y, w,h) self.quitbutton:SetScript("Scripts/GUI/Hyperlink.lua", true) self.quitbutton:SetFontSize(14 * window.display.scale.y) w = 400 * scale h = 550 * scale self.optionspanel = CreateWidget(self.mainpanel,"QUIT", (framebuffer.size.x- w) * 0.5, (framebuffer.size.y - h) * 0.5, w, h) self.optionspanel:SetScript("Scripts/GUI/Panel.lua", true) self.optionspanel.color = Vec4(0.2,0.2,0.2,1) self.optionspanel.border = true self.optionspanel.radius = 8 * scale self.optionspanel.hidden = true  
×
×
  • Create New...