Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement/8 #38

Merged
merged 7 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
58 changes: 24 additions & 34 deletions def/command_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -444,17 +416,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",
Expand All @@ -469,11 +452,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 {
Expand All @@ -494,13 +484,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)
}
}

Expand Down
4 changes: 4 additions & 0 deletions def/enum_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
15 changes: 8 additions & 7 deletions def/enum_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -242,5 +245,3 @@ func NewUntypedEnumValueFromXML(elt *xmlquery.Node) *extenValue {

return &rval
}

const test = "ABC"
53 changes: 28 additions & 25 deletions static_include/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
```
Expand Down Expand Up @@ -53,16 +53,16 @@ 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!
}
```

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<const char*> requiredExtensions = {
Expand All @@ -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,
}
```
Expand All @@ -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`

Expand All @@ -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 {
Expand All @@ -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 {
/* ... */
}
}
Expand Down Expand Up @@ -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).

Expand Down Expand Up @@ -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)
```

Expand All @@ -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.
* 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.
13 changes: 4 additions & 9 deletions static_include/static_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,13 @@ func (r Result) Error() string {
return r.String()
}

type vkCommandKey int
type vkCommand struct {
protoName string
argCount int
hasReturn bool
fnHandle unsafe.Pointer
}

var lazyCommands map[vkCommandKey]vkCommand = make(map[vkCommandKey]vkCommand)

var dlHandle unsafe.Pointer

var overrideLibName string
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down