From 3a621f552ae0f087d689b9c715db65e02f84a438 Mon Sep 17 00:00:00 2001 From: Ben Bredesen Date: Mon, 27 Mar 2023 13:06:00 -0500 Subject: [PATCH 1/6] Result as error, return order from commands --- def/command_type.go | 21 ++++++++++++++++++--- def/enum_type.go | 4 ++++ def/enum_value.go | 15 ++++++++------- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/def/command_type.go b/def/command_type.go index d24a39e..dbe49b9 100644 --- a/def/command_type.go +++ b/def/command_type.go @@ -444,17 +444,28 @@ func (t *commandType) PrintPublicDeclaration(w io.Writer) { } - specStringFromParams := func(sl []*commandParam) string { + specStringFromParams := func(sl []*commandParam) (string, bool) { + var remapResultToError bool = false sb := &strings.Builder{} for _, param := range sl { + if param.resolvedType.RegistryName() == "VkResult" { + remapResultToError = true + continue + } fmt.Fprintf(sb, ", %s %s", param.publicName, param.resolvedType.PublicName()) } - return strings.TrimPrefix(sb.String(), ", ") + + if remapResultToError { + fmt.Fprintf(sb, ", r error") + } + return strings.TrimPrefix(sb.String(), ", "), remapResultToError + } t.bindingParamCount = len(funcTrampolineParams) - inputSpecString, returnSpecString := specStringFromParams(funcInputParams), specStringFromParams(funcReturnParams) + inputSpecString, _ := specStringFromParams(funcInputParams) + returnSpecString, hasResult := specStringFromParams(funcReturnParams) t.PrintDocLink(w) fmt.Fprintf(w, "func %s(%s) (%s) {\n", @@ -469,6 +480,10 @@ func (t *commandType) PrintPublicDeclaration(w io.Writer) { fmt.Fprintf(w, epilogue.String()) + if hasResult { + fmt.Fprint(w, " if r == Result(0) {\nr = SUCCESS\n}\n") + } + if len(funcReturnParams) > 0 { fmt.Fprintf(w, " return\n") } diff --git a/def/enum_type.go b/def/enum_type.go index b20b1b2..b20a27d 100644 --- a/def/enum_type.go +++ b/def/enum_type.go @@ -46,6 +46,10 @@ func (t *enumType) PrintPublicDeclaration(w io.Writer) { sort.Sort(ByValue(t.values)) + if t.RegistryName() == "VkResult" { + fmt.Fprintf(w, "// Command completed successfully\nvar SUCCESS error = nil\n") + } + if len(t.values) > 0 { fmt.Fprint(w, "const (\n") for _, v := range t.values { diff --git a/def/enum_value.go b/def/enum_value.go index 5d823e0..70e1e92 100644 --- a/def/enum_value.go +++ b/def/enum_value.go @@ -57,12 +57,15 @@ func (v *enumValue) Resolve(tr TypeRegistry, vr ValueRegistry) *IncludeSet { } func (v *enumValue) PrintPublicDeclaration(w io.Writer) { - if v.comment != "" { - fmt.Fprintf(w, "// %s\n", v.comment) + // Special case to allow SUCCESS Result to be treated as nil error. Must be separately defined as var, not const + if v.resolvedType.RegistryName() != "VkResult" || v.PublicName() != "SUCCESS" { + fmt.Fprintf(w, "%s %s = %s", v.PublicName(), v.resolvedType.PublicName(), v.ValueString()) + if v.comment != "" { + fmt.Fprintf(w, " // %s\n", v.comment) + } else { + fmt.Fprintln(w) + } } - - fmt.Fprintf(w, "%s %s = %s\n", v.PublicName(), v.resolvedType.PublicName(), v.ValueString()) - } func ReadApiConstantsFromXML(doc *xmlquery.Node, externalType TypeDefiner, tr TypeRegistry, vr ValueRegistry) { @@ -242,5 +245,3 @@ func NewUntypedEnumValueFromXML(elt *xmlquery.Node) *extenValue { return &rval } - -const test = "ABC" From 9ddec819c7f7494f3d7f93b84a9325b589bb3c94 Mon Sep 17 00:00:00 2001 From: Ben Bredesen Date: Mon, 27 Mar 2023 13:41:54 -0500 Subject: [PATCH 2/6] Show handling Result as error in examples --- static_include/README.md | 53 +++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/static_include/README.md b/static_include/README.md index 783ee45..b68edc7 100644 --- a/static_include/README.md +++ b/static_include/README.md @@ -19,8 +19,8 @@ if (r != VK_SUCCESS) { Becomes this: ```go -r, instance := vk.CreateInstance(&instanceCI, nil) -if r != vk.SUCCESS { +instance, err := vk.CreateInstance(&instanceCI, nil) +if err != nil { panic("Could not create a Vulkan instance!") // Don't panic } ``` @@ -53,7 +53,7 @@ for (int i = 0; i < deviceCount; i++) { Yuck. Here's the same code in Go: ```go -if r, devices := vk.EnumeratePhysicalDevices(myInstance); r != vk.SUCCESS { +if devices, err := vk.EnumeratePhysicalDevices(myInstance); err != nil { // handle the error } else { // devices is a slice of vk.PhysicalDevice. Nice! @@ -61,8 +61,8 @@ if r, devices := vk.EnumeratePhysicalDevices(myInstance); r != vk.SUCCESS { ``` But there's more! Passing multiple values to a Vulkan command requires a pointer and count parameter, and sometimes -that count parameter is embedded in another struct. If you are working in -C++, you can handle that a little easier with `std::vector`. For example, specifying requested extensions at instance creation: +that count parameter is embedded in another struct. You can make life a little easier with C++'s `std::vector`. +For example, specifying requested extensions at instance creation: ```C++ std::vector requiredExtensions = { @@ -85,7 +85,8 @@ versus: requiredExtensions := []string{vk.KHR_SWAPCHAIN_EXTENSION_NAME, vk.KHR_SURFACE_EXTENSION_NAME} createInfo := vk.InstanceCreateInfo{ - // No length member, no pointer required, just assign the slice, or even instantiate it inline + // No structure type, no length member, and no pointer required. + // Just assign the slice, or even instantiate it inline EnabledExtensionNames: requiredExtensions, } ``` @@ -99,7 +100,8 @@ executing vk-gen. **This repository does not get direct modifications!** Any bug ## Usage Ensure that your GPU supports Vulkan and that a Vulkan library is installed in your system-default library location -(e.g., C:\windows\system32\vulkan-1.dll on Windows). +(e.g., C:\windows\system32\vulkan-1.dll on Windows). This package uses Cgo to call Vulkan, so it needs to be enabled in +your Go settings. `$ go get github.com/bbredesen/go-vk@latest` @@ -117,7 +119,9 @@ import ( // Notice that you don't need to alias the import, it is already bound to the "vk" namespace func main() { - if r, encodedVersion := vk.EnumerateInstanceVersion(); r != vk.SUCCESS { + if encodedVersion, err := vk.EnumerateInstanceVersion(); err != nil { + // Returned errors are vk.Results. You can directly compare err those predefined values to determine which error occured. + // The string returned by Error() is the name of the code, e.g vk.ERROR_OUT_OF_DATE_KHR.Error() == "ERROR_OUT_OF_DATE_KHR" fmt.Printf("EnumerateInstanceVersion failed! Error code was %s\n", err.Error()) os.Exit(1) } else { @@ -142,20 +146,15 @@ func main() { ApplicationInfo: appInfo, // Extension names are built into the binding as const strings. EnabledExtensionNames: []string{vk.KHR_SURFACE_EXTENSION_NAME, vk.KHR_WIN32_SURFACE_EXTENSION_NAME}, - // Layer names are not though...layer names are not present in the API spec document. + // Layer names are not built in, unfortunately...layers are not part of the core API spec and names are not present in vk.xml EnabledLayerNames: []string{"VK_LAYER_KHRONOS_validation"}, } - r, instance := vk.CreateInstance(&icInfo, nil) - // r is actually a vk.Result, which is itself just an int32. All enumerated - // types, including Result, implement String() so you can easily print the - // value. Because it is returned as a value, not a pointer, you cannot test - // for nil, but you are able to directly compare it to the known error - // codes that Vulkan might return. This is consistent with the Vulkan API, - // but semi-inconsistent with Go-style error checking. - if r != vk.SUCCESS { - fmt.Printf("Failed to create Vulkan instance, error code was %s\n", r.String()) - if r == vk.ERROR_INCOMPATIBLE_DRIVER { + instance, err := vk.CreateInstance(&icInfo, nil) + // vk.SUCCESS is defined as nil, so you can also check for an error like this if preferred. + if err != vk.SUCCESS { + fmt.Printf("Failed to create Vulkan instance, error code was %s\n", err.Error()) + if err == vk.ERROR_INCOMPATIBLE_DRIVER { /* ... */ } } @@ -209,7 +208,8 @@ instanceCI.PNext = unsafe.Pointer(validationFeatures.Vulkanize()) ``` Leaving these as unsafe.Pointers was the simplest implementation to get the binding up and running. The next level of -implementation is to set pNext as a Vulkanizer. I've also considered more specific interfaces flagged with empty functions, +implementation is to define pNext as a Vulkanizer interface type, and have Vulkanize build the chain. I've also +considered more specific interfaces flagged with empty functions, since the spec does indicate for each struct with what other structs it extends (e.g., VkValidationFlagsEXT has a structextends="VkInstanceCreateInfo" attribute). @@ -241,11 +241,15 @@ effectively the same thing. You are free to use whichever method you prefer. In Go 1.20+, this: ```go +ptr, err := vk.MapMemory(/* ... */) + sl := unsafe.Slice((*VertexFormat)(ptr), len(vertices)) copy(sl, vertices) ``` ...is functionally the same as this: ```go +ptr, err := vk.MapMemory(/* ... */) + vk.MemCopySlice(ptr, vertices) ``` @@ -267,13 +271,12 @@ ccv.AsTypeFloat32(float32[4]{0.0, 0.0, 0.0, 1.0}) ## Examples See the [go-vk-samples](https://github.com/bbredesen/go-vk-samples) repo for a number of working Vulkan samples using -this library. The samples currently only run on Windows. +this library. The samples currently run on Windows and Mac. ## Known Issues * VkAccelerationStructureMatrixMotionInstanceNV - embedded bit fields in uint32_t are not handled at all...this structure will not behave as intended and will likely cause a crash if used. -* H.264 and H.265 commands and types are almost certainly broken. There is no structured specification file similar to - vk.xml (that I've found) for the video functions and required types. Instead, the types are directly included through - vk.xml as C headers. As a placeholder, all of these types are defined as int32 through exceptions.json. These types - may end up hard-coded. \ No newline at end of file +* H.264 and H.265 commands and types are almost certainly broken. Vulkan does provide a separate XML file in the vk.xml format for those + types, but reading that file has not yet been implemented in vk-gen. As a placeholder, all of these types are defined + as int32 through exceptions.json. \ No newline at end of file From e94fe16f546ae242af035a314a0f97c40361fe26 Mon Sep 17 00:00:00 2001 From: Ben Bredesen Date: Tue, 28 Mar 2023 10:54:23 -0500 Subject: [PATCH 3/6] remove lazyCommands map and commandKey type --- static_include/static_common.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/static_include/static_common.go b/static_include/static_common.go index e272acb..87d40f3 100644 --- a/static_include/static_common.go +++ b/static_include/static_common.go @@ -38,7 +38,6 @@ func (r Result) Error() string { return r.String() } -type vkCommandKey int type vkCommand struct { protoName string argCount int @@ -46,8 +45,6 @@ type vkCommand struct { fnHandle unsafe.Pointer } -var lazyCommands map[vkCommandKey]vkCommand = make(map[vkCommandKey]vkCommand) - var dlHandle unsafe.Pointer var overrideLibName string @@ -60,7 +57,7 @@ func OverrideDefaultVulkanLibrary(nameOrPath string) { overrideLibName = nameOrPath } -func execTrampoline(commandKey vkCommandKey, args ...uintptr) uintptr { +func execTrampoline(cmd *vkCommand, args ...uintptr) uintptr { if dlHandle == nil { var libName string switch runtime.GOOS { @@ -86,10 +83,10 @@ func execTrampoline(commandKey vkCommandKey, args ...uintptr) uintptr { C.free(unsafe.Pointer(cstr)) } - cmd := lazyCommands[commandKey] + // cmd := lazyCommands[commandKey] if cmd.fnHandle == nil { cmd.fnHandle = C.SymbolFromName(dlHandle, unsafe.Pointer(sys_stringToBytePointer(cmd.protoName))) - lazyCommands[commandKey] = cmd + // lazyCommands[commandKey] = cmd } if len(args) != cmd.argCount { @@ -126,9 +123,7 @@ func execTrampoline(commandKey vkCommandKey, args ...uintptr) uintptr { panic("Unhandled number of arguments passed for cmd " + cmd.protoName) } - // Trampoline is always returning a file does not exist error in the second return value, so that error reporting is disabled - - return uintptr(result) //, err + return uintptr(result) } func stringToNullTermBytes(s string) *byte { From e88387593d2ab03efc7c804b89858989206b5168 Mon Sep 17 00:00:00 2001 From: Ben Bredesen Date: Tue, 28 Mar 2023 10:55:26 -0500 Subject: [PATCH 4/6] Change trampoline call from map key to var --- def/command_type.go | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/def/command_type.go b/def/command_type.go index d24a39e..a395c38 100644 --- a/def/command_type.go +++ b/def/command_type.go @@ -444,17 +444,28 @@ func (t *commandType) PrintPublicDeclaration(w io.Writer) { } - specStringFromParams := func(sl []*commandParam) string { + specStringFromParams := func(sl []*commandParam) (string, bool) { + var remapResultToError bool = false sb := &strings.Builder{} for _, param := range sl { + if param.resolvedType.RegistryName() == "VkResult" { + remapResultToError = true + continue + } fmt.Fprintf(sb, ", %s %s", param.publicName, param.resolvedType.PublicName()) } - return strings.TrimPrefix(sb.String(), ", ") + + if remapResultToError { + fmt.Fprintf(sb, ", r error") + } + return strings.TrimPrefix(sb.String(), ", "), remapResultToError + } t.bindingParamCount = len(funcTrampolineParams) - inputSpecString, returnSpecString := specStringFromParams(funcInputParams), specStringFromParams(funcReturnParams) + inputSpecString, _ := specStringFromParams(funcInputParams) + returnSpecString, hasResult := specStringFromParams(funcReturnParams) t.PrintDocLink(w) fmt.Fprintf(w, "func %s(%s) (%s) {\n", @@ -469,11 +480,18 @@ func (t *commandType) PrintPublicDeclaration(w io.Writer) { fmt.Fprintf(w, epilogue.String()) + if hasResult { + fmt.Fprint(w, " if r == Result(0) {\nr = SUCCESS\n}\n") + } + if len(funcReturnParams) > 0 { fmt.Fprintf(w, " return\n") } fmt.Fprintf(w, "}\n\n") + + fmt.Fprintf(w, "var %s = &vkCommand{\"%s\", %d, %v, nil}\n", + t.RegistryName(), t.RegistryName(), t.bindingParamCount, t.resolvedReturnType != nil) } func trampStringFromParams(sl []*commandParam) string { @@ -494,13 +512,13 @@ func (t *commandType) printTrampolineCall(w io.Writer, trampParams []*commandPar if returnParam != nil { if returnParam.resolvedType.IsIdenticalPublicAndInternal() { - fmt.Fprintf(w, " %s = %s(execTrampoline(%s%s))\n", returnParam.publicName, returnParam.resolvedType.PublicName(), t.keyName(), trampParamsString) + fmt.Fprintf(w, " %s = %s(execTrampoline(%s%s))\n", returnParam.publicName, returnParam.resolvedType.PublicName(), t.RegistryName(), trampParamsString) } else { - fmt.Fprintf(w, " rval := %s(execTrampoline(%s%s))\n", returnParam.resolvedType.InternalName(), t.keyName(), trampParamsString) + fmt.Fprintf(w, " rval := %s(execTrampoline(%s%s))\n", returnParam.resolvedType.InternalName(), t.RegistryName(), trampParamsString) fmt.Fprintf(w, " %s = %s\n", returnParam.publicName, returnParam.resolvedType.TranslateToPublic("rval")) } } else { - fmt.Fprintf(w, " execTrampoline(%s%s)\n", t.keyName(), trampParamsString) + fmt.Fprintf(w, " execTrampoline(%s%s)\n", t.RegistryName(), trampParamsString) } } From 19d2a8b43f55869b068fc81aadb5eed253a1235a Mon Sep 17 00:00:00 2001 From: Ben Bredesen Date: Tue, 28 Mar 2023 11:07:59 -0500 Subject: [PATCH 5/6] Remove global and init content --- def/command_type.go | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/def/command_type.go b/def/command_type.go index a395c38..6ba36c4 100644 --- a/def/command_type.go +++ b/def/command_type.go @@ -83,34 +83,6 @@ func (t *commandType) Resolve(tr TypeRegistry, vr ValueRegistry) *IncludeSet { return iset } -func (t *commandType) PrintGlobalDeclarations(w io.Writer, idx int, isStart bool) { - if t.staticCodeRef != "" || t.IsAlias() { - // Ignored, static refs from exceptions.json aren't processed through - // Cgo/lazyCommands and no need to write key entries for alias commands - return - } - - if isStart { - if idx == 0 { - fmt.Fprintf(w, "%s vkCommandKey = iota\n", t.keyName()) - } else { - fmt.Fprintf(w, "%s vkCommandKey = iota + %d\n", t.keyName(), idx) - } - } else { - fmt.Fprintln(w, t.keyName()) - } -} - -func (t *commandType) PrintFileInitContent(w io.Writer) { - if t.IsAlias() { - // No need to write key entries for alias commands - return - } - fmt.Fprintf(w, "lazyCommands[%s] = vkCommand{\"%s\", %d, %v, nil}\n", - t.keyName(), t.RegistryName(), t.bindingParamCount, t.resolvedReturnType != nil) - -} - func (t *commandType) PrintPublicDeclaration(w io.Writer) { if t.staticCodeRef != "" { fmt.Fprintf(w, "// %s is static code, not generated from vk.xml; aliased to %s\n", t.PublicName(), t.staticCodeRef) From 533cf1af3d8dd6d749f53a077b0d9ac131e02a08 Mon Sep 17 00:00:00 2001 From: Ben Bredesen Date: Tue, 28 Mar 2023 12:00:44 -0500 Subject: [PATCH 6/6] Add version specific XML download instructions --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 9c4269c..47caf95 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ Install: `go install github.com/bbredesen/vk-gen@latest` Download the latest registry file: `curl https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/registry/vk.xml > vk.xml` +(Or, replace "main" in the URL above with the tagged version you want to generate against: e.g., +`https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/v1.2.203/registry/vk.xml` for the last version 1.2 specification.) + Run the tool: `vk-gen` Use `-inFile` to specify a registry filename or path (defaults to `./vk.xml`)