创建实例#
我们来创建 Vulkan 实例。
添加一个字段保存VkInstance。
private MemorySegment window = MemorySegment.NULL;
private VkInstance instance = null;
释放资源。
private void disposeVulkan() {
if (instance != null) {
vkDestroyInstance(instance, MemorySegment.NULL);
}
}
使用 Vulkan 的函数前需要先指定如何加载函数。在initVulkan中添加代码。
private void initVulkan() {
VK.create(GLFW::glfwGetInstanceProcAddress);
}
接下来就是重点了:填充结构体。
内存栈#
我们不能每次都在堆上分配内存,那样会导致程序变卡。OverrunGL 引入了新的段分配器:MemoryStack。MemoryStack实际是把已分配的内存切片,这样我们就不必反复分配少量内存。
创建createInstance方法。
private void initVulkan() {
// ...
createInstance();
}
private void createInstance() {
try (MemoryStack stack = MemoryStack.pushLocal()) {
}
}
结构体#
OverrunGL 封装了结构体以便我们读写。
在栈上创建一个VkApplicationInfo。
try (MemoryStack stack = MemoryStack.pushLocal()) {
var applicationInfo = VkApplicationInfo.alloc(stack)
.sType(VK_STRUCTURE_TYPE_APPLICATION_INFO)
.pApplicationName(stack.allocateFrom("Vulkan in Java"))
.applicationVersion(VK_MAKE_API_VERSION(0, 1, 0, 0))
.pEngineName(stack.allocateFrom("No Engine"))
.engineVersion(VK_MAKE_API_VERSION(0, 0, 0, 1))
.apiVersion(VK_API_VERSION_1_0);
}
只有apiVersion重要,其他的可以无视。
然后创建VkInstanceCreateInfo。
var applicationInfo = ...;
var createInfo = VkInstanceCreateInfo.alloc(stack)
.sType(VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO)
.pApplicationInfo(applicationInfo.segment())
.enabledLayerCount(0)
.ppEnabledLayerNames(MemorySegment.NULL)
.enabledExtensionCount(0)
.ppEnabledExtensionNames(MemorySegment.NULL);
pApplicationInfo需要传入applicationInfo的地址。这里ppEnabledLayerNames和ppEnabledExtensionNames留着备用。
内存布局#
前面提到内存布局描述内存段的内容的布局。FFM API 自带了以下几种布局:
| 布局 | 值类型 | C 类型 |
|---|---|---|
| ValueLayout.JAVA_BOOLEAN | boolean | char |
| ValueLayout.JAVA_CHAR | char | short |
| ValueLayout.JAVA_BYTE | byte | char |
| ValueLayout.JAVA_SHORT | short | short |
| ValueLayout.JAVA_INT | int | int |
| ValueLayout.JAVA_LONG | long | long long |
| ValueLayout.JAVA_FLOAT | float | float |
| ValueLayout.JAVA_DOUBLE | double | double |
| ValueLayout.ADDRESS | MemorySegment | void* |
除此之外,OverrunGL 封装的结构体包含一个LAYOUT字段,描述结构体的布局。
现在创建实例。
var createInfo = ...;
var p = stack.allocate(ValueLayout.ADDRESS);
vkCreateInstance(createInfo.segment(), MemorySegment.NULL, p);
instance = new VkInstance(p.get(ValueLayout.ADDRESS, 0));
首先创建布局为ADDRESS的临时内存p,用于保存一个MemorySegment。我们通过get方法获取本机库返回的值。
注意
p只负责保存 Vulkan 实例的地址,get方法才真正获取实例的地址。
Util#
每次都要创建临时内存实在太麻烦了,有没有简便方法呢?没有。但是,我们可以封装代码。
创建VkUtil类。acquireAddress用于获取地址。acquireLong之后会用上。
public class VkUtil {
private VkUtil() {
}
public static void check(String message, int result) {
if (result != VK_SUCCESS) {
throw new IllegalStateException(result + ": " + message);
}
}
public static MemorySegment acquireAddress(String message, ToIntFunction<MemorySegment> func) {
try (MemoryStack stack = MemoryStack.pushLocal()) {
var p = stack.allocate(ValueLayout.ADDRESS);
check(message, func.applyAsInt(p));
return p.get(ValueLayout.ADDRESS, 0);
}
}
public static int acquireInt(String message, ToIntFunction<MemorySegment> func) {
try (MemoryStack stack = MemoryStack.pushLocal()) {
var p = stack.allocate(ValueLayout.JAVA_INT);
check(message, func.applyAsInt(p));
return p.get(ValueLayout.JAVA_INT, 0);
}
}
public static long acquireLong(String message, ToIntFunction<MemorySegment> func) {
try (MemoryStack stack = MemoryStack.pushLocal()) {
var p = stack.allocate(ValueLayout.JAVA_LONG);
check(message, func.applyAsInt(p));
return p.get(ValueLayout.JAVA_LONG, 0);
}
}
}
现在修改创建实例的代码。
var createInfo = ...;
instance = new VkInstance(VkUtil.acquireAddress("failed to create instance",
p -> vkCreateInstance(createInfo.segment(), MemorySegment.NULL, p)
));
验证层#
Vulkan SDK 自带的验证层能帮助我们调试代码。
修改App.java。
private static final App INSTANCE = new App();
private static final boolean ENABLE_VALIDATION_LAYERS = true;
private static final String[] VALIDATION_LAYERS = {"VK_LAYER_KHRONOS_validation"};
检查是否支持验证层。我们用MemoryUtil::nativeString把内存段转换成字符串。
private boolean isValidationLayersSupported() {
try (MemoryStack stack = MemoryStack.pushLocal()) {
int layerCount = VkUtil.acquireInt("failed to get instance layer property count",
p -> vkEnumerateInstanceLayerProperties(p, MemorySegment.NULL)
);
var properties = VkLayerProperties.alloc(stack, layerCount);
VkUtil.check("failed to enumerate instance layer properties",
vkEnumerateInstanceLayerProperties(stack.ints(layerCount), properties.segment()));
for (String layer : VALIDATION_LAYERS) {
boolean found = false;
for (int i = 0; i < layerCount; i++) {
if (layer.equals(MemoryUtil.nativeString(properties.layerNameAt(i)))) {
found = true;
break;
}
}
if (!found) {
System.err.println("couldn't find layer " + layer);
return false;
}
}
return true;
}
}
private void createInstance() {
if (ENABLE_VALIDATION_LAYERS && !isValidationLayersSupported()) {
throw new IllegalStateException("requested validation layers but is not found");
}
// ...
}
更新VkInstanceCreateInfo。用MemoryUtil::allocArray创建字符串数组。
var createInfo = VkInstanceCreateInfo.alloc(stack)
// ...
.enabledLayerCount(ENABLE_VALIDATION_LAYERS ? VALIDATION_LAYERS.length : 0)
.ppEnabledLayerNames(ENABLE_VALIDATION_LAYERS ? MemoryUtil.allocArray(stack, VALIDATION_LAYERS) : MemorySegment.NULL)
// ...
;
实例扩展#
继续修改App.java。
private boolean isValidationLayersSupported() {
// ...
}
private List<String> getInstanceExtensions() {
List<String> list = new ArrayList<>();
try (MemoryStack stack = MemoryStack.pushLocal()) {
MemorySegment pCount = stack.allocate(ValueLayout.JAVA_INT);
MemorySegment extensions = glfwGetRequiredInstanceExtensions(pCount);
int count = pCount.get(ValueLayout.JAVA_INT, 0);
MemorySegment reinterpret = extensions.reinterpret(ValueLayout.ADDRESS.scale(0, count));
for (int i = 0; i < count; i++) {
list.add(MemoryUtil.nativeString(reinterpret.getAtIndex(ValueLayout.ADDRESS, i)));
}
}
if (ENABLE_VALIDATION_LAYERS) {
list.add(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
try (MemoryStack stack = MemoryStack.pushLocal()) {
int extCount = VkUtil.acquireInt("failed to get instance extension count",
p -> vkEnumerateInstanceExtensionProperties(MemorySegment.NULL, p, MemorySegment.NULL));
var properties = VkExtensionProperties.alloc(stack, extCount);
VkUtil.check("failed to enumerate instance extension properties",
vkEnumerateInstanceExtensionProperties(MemorySegment.NULL, stack.ints(extCount), properties.segment()));
for (String extName : list) {
boolean found = false;
for (int i = 0; i < extCount; i++) {
if (extName.equals(MemoryUtil.nativeString(properties.extensionNameAt(i)))) {
found = true;
break;
}
}
if (!found) {
System.err.println("couldn't find extension " + extName);
return List.of();
}
}
}
return list;
}
private void createInstance() {
if (ENABLE_VALIDATION_LAYERS && !isValidationLayersSupported()) {
throw new IllegalStateException("requested validation layers but is not found");
}
List<String> instanceExtensions = getInstanceExtensions();
if (instanceExtensions.isEmpty()) {
throw new IllegalStateException("required instance extensions not found");
}
// ...
try (MemoryStack stack = MemoryStack.pushLocal()) {
// ...
var createInfo = VkInstanceCreateInfo.alloc(stack)
// ...
.enabledExtensionCount(ENABLE_VALIDATION_LAYERS ? instanceExtensions.size() : 0)
.ppEnabledExtensionNames(ENABLE_VALIDATION_LAYERS ? MemoryUtil.allocArray(stack, instanceExtensions.toArray(String[]::new)) : MemorySegment.NULL);
// ...
}
}
调试信使#
调试信使可以输出供调试用的信息。
创建VkDebugMessenger类。
public class VkDebugMessenger {
private static final MemorySegment UPCALL_STUB = VkDebugUtilsMessengerCallbackEXT.alloc(Arena.global(), VkDebugMessenger::callback);
private VkInstance instance = null;
private long handle = VK_NULL_HANDLE;
private static int callback(
int messageSeverity,
int messageTypes,
MemorySegment pCallbackData/* (1)! */,
MemorySegment pUserData
) {
String message = MemoryUtil.nativeString(VkDebugUtilsMessengerCallbackDataEXT.pMessage(pCallbackData.reinterpret(VkDebugUtilsMessengerCallbackDataEXT.LAYOUT.byteSize()), 0));
switch (messageSeverity) {
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT -> System.err.println("DM TRACE: " + message);
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT -> System.err.println("DM INFO : " + message);
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT -> System.err.println("DM WARN : " + message);
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT -> System.err.println("DM ERROR: " + message);
}
return VK_FALSE;
}
public static VkDebugUtilsMessengerCreateInfoEXT createInfo(SegmentAllocator allocator) {
return VkDebugUtilsMessengerCreateInfoEXT.alloc(allocator)
.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(UPCALL_STUB);
}
public void create(VkInstance instance) {
this.instance = instance;
try (MemoryStack stack = MemoryStack.pushLocal()) {
var createInfo = createInfo(stack);
handle = VkUtil.acquireLong("failed to create debug utils messenger",
p -> vkCreateDebugUtilsMessengerEXT(instance, createInfo.segment(), MemorySegment.NULL, p));
}
}
public void destroy() {
if (instance != null) {
vkDestroyDebugUtilsMessengerEXT(instance, handle, MemorySegment.NULL);
}
}
public VkInstance instance() {
return instance;
}
public long handle() {
return handle;
}
}
- 使用静态方法访问,必须修改内存段的大小!
再修改App.java。
private VkInstance instance = null;
private final VkDebugMessenger debugMessenger = new VkDebugMessenger();
private void initVulkan() {
VK.create(GLFW::glfwGetInstanceProcAddress);
createInstance();
if (ENABLE_VALIDATION_LAYERS) {
debugMessenger.create(instance);
}
}
private void createInstance() {
// ...
try (MemoryStack stack = MemoryStack.pushLocal()) {
// ...
var createInfo = VkInstanceCreateInfo.alloc(stack)
.sType(VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO)
.pNext(ENABLE_VALIDATION_LAYERS ? VkDebugMessenger.createInfo(stack).segment() : MemorySegment.NULL)
// ...
;
// ...
}
}
private void disposeVulkan() {
if (ENABLE_VALIDATION_LAYERS && debugMessenger != null) {
debugMessenger.destroy();
}
// ...
}
