vkGetInstanceProcAddr doesn't find function address[SOLVED]

Started by ilum, March 04, 2019, 20:53:56

Previous topic - Next topic

ilum

Hi,

    I've got a simple function that helps set up debugging.
private int createDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerCreateInfoEXT pCreateInfo, VkAllocationCallbacks pAllocator) {
        long functionHandle = vkGetInstanceProcAddr(instance, memUTF8("vkCreateDebugUtilsMessengerEXT"));
        if (functionHandle != NULL) {
            try(MemoryStack stack = MemoryStack.stackPush()) {
                LongBuffer pDebugMessenger = stack.callocLong(1);
                int result = vkCreateDebugUtilsMessengerEXT(instance, pCreateInfo, pAllocator, pDebugMessenger);
                debugMessengerHandle = pDebugMessenger.get(0);

                return result;
            }
        }

        return VK_ERROR_EXTENSION_NOT_PRESENT;
    }

    As mentioned in the title vkGetInstanceProcAddr(instance, memUTF8("vkCreateDebugUtilsMessengerEXT")) always returns 0 so i never get a pointer to that function. Any idea why ? Is there something I have to configure or import ? I've written a C++ version and it works. I'm using lwjgl 3.2.1

spasi

Have you added VK_EXT_DEBUG_REPORT_EXTENSION_NAME to VkInstanceCreateInfo's ppEnabledExtensionNames? EXT_debug_report functions will not be available if you didn't.

Also a note about the above code, there's a memory leak in this line:

long functionHandle = vkGetInstanceProcAddr(instance, memUTF8("vkCreateDebugUtilsMessengerEXT"));


The memUTF8 call creates a ByteBuffer using malloc. That memory is never freed in your code. You may want to switch to the vkGetInstanceProcAddr overload that accepts a CharSequence. It uses the MemoryStack internally, which is more efficient (no malloc/free overhead) and leak-proof.

ilum

Quote
Have you added VK_EXT_DEBUG_REPORT_EXTENSION_NAME to VkInstanceCreateInfo's ppEnabledExtensionNames?
It's added but I probably did something wrong. I'll debug tomorrow.

Quote
The memUTF8 call creates a ByteBuffer using malloc. That memory is never freed in your code. You may want to switch to the vkGetInstanceProcAddr overload that accepts a CharSequence. It uses the MemoryStack internally, which is more efficient (no malloc/free overhead) and leak-proof.
Thanks. Still getting used to lwjgl. I'm a java programmer by trade but for the moment I find it harder to reason about memory management than in C++ which I guess is normal. Each time I create a PointerBuffer and pass it to a structure binding I don't know if I can free it while the structure is still in use. I'll have to take a look in the api to see if a new memory zone is created or they are both pointing at the same space, I've just been converting example code without paying much attention up until now. Regarding the memory leak, it's true that I haven't freed the ByteBuffer but aren't [insert type here]Buffers GC'ed and the underlying off-heap memory would be released at that point ? So it would just stress the GC rather than a memory leak...

spasi

Quote from: parvu_eduard on March 04, 2019, 21:52:13but aren't [insert type here]Buffers GC'ed and the underlying off-heap memory would be released at that point ? So it would just stress the GC rather than a memory leak...

That's true of buffers allocated via BufferUtils. It delegates to ByteBuffer.allocateDirect, which allocates a buffer that is indeed tracked by the GC. LWJGL supports 3 different ways to allocate:

- BufferUtils, freed automatically by the GC.
- MemoryUtil memAlloc, memFree, etc., user needs to track buffer usage and manually free. These methods call down to malloc/calloc/free/etc. directly and wrap the returned pointer in a buffer instance. The allocator used can also be configured with Configuration.MEMORY_ALLOCATOR. The memUTF8 method does a memAlloc internally, that's why you get a memory leak if you never memFree the buffer.
- MemoryStack for small/local allocations, the memory is statically allocated to each thread and never freed. The user simply pushes/pops stack frames (usually a try-with-resources block is used, which pops automatically).

Read Memory management in LWJGL 3 for more details.

ilum

QuoteHave you added VK_EXT_DEBUG_REPORT_EXTENSION_NAME to VkInstanceCreateInfo's ppEnabledExtensionNames? EXT_debug_report functions will not be available if you didn't.

I've printed out the ppEnabledExtensionNames and I get these 3: VK_KHR_surface, VK_KHR_win32_surface, VK_EXT_debug_report . The issue still persists. I'll add the whole class code for further details.

import org.lwjgl.PointerBuffer;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.vulkan.*;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.util.ArrayList;
import java.util.List;

import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.glfw.GLFWVulkan.glfwGetRequiredInstanceExtensions;
import static org.lwjgl.system.MemoryUtil.*;
import static org.lwjgl.vulkan.EXTDebugReport.VK_EXT_DEBUG_REPORT_EXTENSION_NAME;
import static org.lwjgl.vulkan.EXTDebugUtils.*;
import static org.lwjgl.vulkan.VK10.*;

public class HelloTriangleApplication {

    private long window;
    private VkInstance vkInstance;
    private long debugMessengerHandle = VK_NULL_HANDLE;

    private boolean enableValidationLayers = true;

    private static List<String> validationLayers;

    static {
        validationLayers = new ArrayList<>(1);
        validationLayers.add("VK_LAYER_LUNARG_standard_validation");
    }

    void run(boolean enableValidationLayers) {
        this.enableValidationLayers = enableValidationLayers;
        initWindow();
        initVulkan();
        mainLoop();
        cleanup();
    }

    private void initWindow() {
        glfwInit();
        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
        glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

        final int WIDTH = 800;
        final int HEIGHT = 600;
        final String TITLE = "Vulkan";

        window = glfwCreateWindow(WIDTH, HEIGHT, TITLE, NULL, NULL);
    }

    private void initVulkan() {
        createInstance();
        setupDebugMessenger();
    }

    private void mainLoop() {
        while (!glfwWindowShouldClose(window)) {
            glfwPollEvents();
        }
    }

    private void cleanup() {
        if (enableValidationLayers) {
            destroyDebugUtilsMessengerExtension(vkInstance, debugMessengerHandle, null);
        }

        vkDestroyInstance(vkInstance, null);

        glfwDestroyWindow(window);
        glfwTerminate();
    }

    private void createInstance() {
        if (enableValidationLayers && !checkValidationLayerSupport()) {
            throw new AssertionError("validation layers requested, but not available!");
        }

        try(MemoryStack stack = MemoryStack.stackPush()) {
            final ByteBuffer applicationName = memUTF8("Hello Triangle!");
            final ByteBuffer engineName = memUTF8("No engine!");

            VkApplicationInfo applicationInfo = VkApplicationInfo.callocStack(stack)
                .sType(VK_STRUCTURE_TYPE_APPLICATION_INFO)
                .pApplicationName(applicationName)
                .applicationVersion(VK_MAKE_VERSION(1, 0, 0))
                .pEngineName(engineName)
                .engineVersion(VK_MAKE_VERSION(1, 0, 0))
                .apiVersion(VK_API_VERSION_1_0);

            final PointerBuffer enabledExtensionNames = getRequiredExtensions();

            VkInstanceCreateInfo createInfo = VkInstanceCreateInfo.callocStack(stack)
                .sType(VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO)
                .pApplicationInfo(applicationInfo)
                .ppEnabledExtensionNames(enabledExtensionNames);

            logExtensionNames(createInfo);

            PointerBuffer pInstance = stack.mallocPointer(1);

            int vkResult = vkCreateInstance(createInfo, null, pInstance);

            if (vkResult != VK_SUCCESS) {
                throw new AssertionError("Failed to create vk instance!");
            }

            vkInstance = new VkInstance(pInstance.get(0), createInfo);

            ////////
            memFree(applicationName);
            memFree(engineName);
            memFree(enabledExtensionNames);
        }
    }

    private void setupDebugMessenger() {
        if (enableValidationLayers) {
            try(MemoryStack stack = MemoryStack.stackPush()) {
                VkDebugUtilsMessengerCreateInfoEXT createInfoEXT = VkDebugUtilsMessengerCreateInfoEXT.callocStack(stack)
                        .sType(VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT)
                        .messageSeverity(VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
                        .messageType(VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT)
                        .pfnUserCallback(new VkDebugUtilsMessengerCallbackEXT() {
                            @Override
                            public int invoke(int messageSeverity, int messageTypes, long pCallbackData, long pUserData) {
                                System.err.println("validation layer: " + pCallbackData); // need a util class to translate these ints into messages

                                return VK_FALSE;
                            }
                        });

                if (createDebugUtilsMessengerExtension(vkInstance, createInfoEXT, null) != VK_SUCCESS) {
                    throw new AssertionError("Failed to set up debug messenger!");
                }
            }
        }
    }

    private int createDebugUtilsMessengerExtension(VkInstance instance, VkDebugUtilsMessengerCreateInfoEXT pCreateInfo, VkAllocationCallbacks pAllocator) {
        long functionHandle = vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
        if (functionHandle != NULL) {
            try(MemoryStack stack = MemoryStack.stackPush()) {
                LongBuffer pDebugMessenger = stack.mallocLong(1);
                int result = vkCreateDebugUtilsMessengerEXT(instance, pCreateInfo, pAllocator, pDebugMessenger);
                debugMessengerHandle = pDebugMessenger.get(0);

                return result;
            }
        }

        return VK_ERROR_EXTENSION_NOT_PRESENT;
    }

    private void destroyDebugUtilsMessengerExtension(VkInstance instance, long debugMessenger, VkAllocationCallbacks pAllocator) {
        long functionHandle = vkGetInstanceProcAddr(instance,"vkDestroyDebugUtilsMessengerEXT");
        if (functionHandle != NULL) {
            vkDestroyDebugUtilsMessengerEXT(instance, debugMessenger, pAllocator);
        }
    }

    private PointerBuffer getRequiredExtensions() {
        PointerBuffer requiredInstanceExtensions = glfwGetRequiredInstanceExtensions();

        if (requiredInstanceExtensions == null) {
            throw new AssertionError("Failed to find list of required vulkan extensions");
        }

        final ByteBuffer VK_EXT_DEBUG_REPORT_EXTENSION = memUTF8(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);

        PointerBuffer ppEnabledExtensionNames = memAllocPointer(requiredInstanceExtensions.remaining() + 1);
        ppEnabledExtensionNames.put(requiredInstanceExtensions);
        ppEnabledExtensionNames.put(VK_EXT_DEBUG_REPORT_EXTENSION);
        ppEnabledExtensionNames.flip();

        //////
        memFree(VK_EXT_DEBUG_REPORT_EXTENSION);

        return ppEnabledExtensionNames;
    }

    private boolean checkValidationLayerSupport() {
        try(MemoryStack stack = MemoryStack.stackPush()) {
            IntBuffer layerCount = stack.mallocInt(1);
            vkEnumerateInstanceLayerProperties(layerCount, null);

            VkLayerProperties.Buffer availableValidationLayers = VkLayerProperties.mallocStack(layerCount.get(0), stack);
            vkEnumerateInstanceLayerProperties(layerCount, availableValidationLayers);

            for (String validationLayer : validationLayers) {
                boolean layerFound = false;

                while (availableValidationLayers.hasRemaining()) {
                    if (validationLayer.equals(availableValidationLayers.get().layerNameString())) {
                        layerFound = true;
                        break;
                    }
                }

                if (!layerFound) {
                    return false;
                }
            }

            return true;
        }
    }

    private void logExtensionNames(VkInstanceCreateInfo createInfo) {
        // see extensions names
        PointerBuffer e =  createInfo.ppEnabledExtensionNames();

        if (e == null) {
            throw new AssertionError("No extension names found!");
        }

        while (e.hasRemaining()){
            System.out.println(e.getStringUTF8());
        }
    }
}

spasi

Sorry, you need to add VK_EXT_DEBUG_UTILS_EXTENSION_NAME for vkCreateDebugUtilsMessengerEXT.

Btw, unless you're doing the function lookup manually to familiarize yourself with the low-level details, LWJGL has already done this work for you in the VKCapabilities<Instance/Device> classes. For example, you can check if EXT_debug_utils is available with:

if (instance.getCapabilities().VK_EXT_debug_utils) { ... }


or an entry point directly (usually not necessary) with:

if (instance.getCapabilities().vkCreateDebugUtilsMessengerEXT != NULL) { ... }

ilum

QuoteSorry, you need to add VK_EXT_DEBUG_UTILS_EXTENSION_NAME for vkCreateDebugUtilsMessengerEXT.
Thank you again. That did the trick.

QuoteBtw, unless you're doing the function lookup manually to familiarize yourself with the low-level details, LWJGL has already done this work for you in the VKCapabilities<Instance/Device> classes
That is good to know.