Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
serenity4 committed Jul 14, 2024
2 parents 60d1688 + 7988dd3 commit db421fe
Show file tree
Hide file tree
Showing 17 changed files with 215 additions and 14 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog for Vulkan.jl

## Version `v0.6.16`
- ![Feature][badge-feature] Dependencies between handles may be specified via `Vk.depends_on(x, handle)`, to ensure that a given handle is not destroyed before anything that depends on it. This leverages the reference counting system already implemented, which itself encodes such dependencies from a given parent handle and its children. See the docstring of `Vk.depends_on` for more details.

## Version `v0.6.14`
- ![Feature][badge-feature] New mappings between Julia types and Vulkan formats are available, via `Vk.Format` constructors and `Vk.format_type` functions.

Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Vulkan"
uuid = "9f14b124-c50e-4008-a7d4-969b3a6cd68a"
authors = ["Cédric Belmant"]
version = "0.6.14"
version = "0.6.16"

[deps]
Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697"
Expand Down
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ makedocs(;
"Explanations" => [
"Motivations" => "about/motivations.md",
"Extension mechanism" => "about/extension_mechanism.md",
"Library loading" => "about/library_loading.md",
],
"API" => "api.md",
"Utility" => "utility.md",
Expand Down
4 changes: 2 additions & 2 deletions docs/src/about/extension_mechanism.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Optional functionality

Vulkan uses a particular functionality mechanism based on [features](https://www.khronos.org/registry/vulkan/specs/1.2/html/vkspec.html#features), [extensions](https://www.khronos.org/registry/vulkan/specs/1.2/html/vkspec.html#extendingvulkan) and properties.
Vulkan uses a particular functionality mechanism based on [features](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap47.html), [extensions](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap46.html) and properties.

Properties are per-device, and are not specified by the user; instead, they are returned by the Vulkan corresponding driver. Features may be very similar to properties semantically: they may specify whether some functionality is available or not on the device, such as atomic operations. However, features are usually more complex than that: the presence or absence of specific features will cause the driver to behave differently. Therefore, the difference with properties is that enabling a feature may dynamically change the logic of the driver, while properties are static and can only tell whether some functionality is supported or not.

SPIR-V uses a similar mechanism, with capabilities (analogous to features) and extensions. However, one should note that SPIR-V is a format for GPU programs, and not an API in itself; there is no SPIR-V driver of any kind. Therefore, any configuration for SPIR-V will be specified through its execution environment, e.g. OpenCL or Vulkan. As a result, certain Vulkan features and extensions are directly related to SPIR-V capabilities and extensions.

As a client API for SPIR-V, Vulkan [establishes](https://www.khronos.org/registry/vulkan/specs/1.2/html/vkspec.html#spirvenv) what SPIR-V capabilities and extensions are enabled given the level of functionality requested from or provided by the driver. Notably, no SPIR-V capability or extension can be enabled without a corresponding requirement for a Vulkan core version or the presence of a Vulkan feature or extension.
As a client API for SPIR-V, Vulkan [establishes](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap52.html) what SPIR-V capabilities and extensions are enabled given the level of functionality requested from or provided by the driver. Notably, no SPIR-V capability or extension can be enabled without a corresponding requirement for a Vulkan core version or the presence of a Vulkan feature or extension.

Optional SPIR-V functionality is therefore fully implicit, based on the Vulkan API configuration. To help automate this mapping (and alleviate or even remove the burden forced on the developer), `SPIRV_CAPABILITIES` and `SPIRV_EXTENSIONS` are exported which contain information about capability and extension requirements, respectively.
45 changes: 45 additions & 0 deletions docs/src/about/library_loading.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Library loading

Owing to its extensible architecture, Vulkan may require additional libraries to be available during runtime. That will be
notably the case of every layer, and of most [WSI (Window System Integration)](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap34.html)
instance extensions which require hooking into the OS' windowing system.

It is important to know where these libraries come from, to avoid crashes and ensure correct behavior.
A notable case for failure is when some code uses a new function exposed in a recent library release, but the loaded library is too old.
In particular, this may occur after updating Vulkan drivers, or upgrading the OS (which in turn updates OS-specific libraries and possibly the Vulkan loader which may then rely on these updates).
Other than that, libraries are generally backward compatible, and compatibility issues are fairly rare.

In Julia, there are two notable systems that may provide them:
- Your operating system, using whatever is available, as matched first by the linker depending on configuration. Version suffixes (e.g. `libvulkan.so.1`) may be used to provide weak compatibility guarantees.
- Pkg's [artifact system](https://pkgdocs.julialang.org/v1/artifacts/), providing libraries and binaries with set versions and stronger compatibility guarantees with semantic versioning. The artifact system explicitly uses libraries from other artifacts, *and not from the system*. Keep that in mind especially if you rely on artifacts for application-level functionality (e.g. GLFW).

When a library is required by a Vulkan feature, extension or layer, it will most likely use the first one already loaded.
That may be an artifact, or a system library. Relying on either comes with caveats:
- Relying on an artifact may incorrectly interface with OS-specific functionality, which requires to match system libraries.
- Relying on system libraries may cause compatibility issues when using artifacts that require specific versions.

A reasonable recommendation would be to go for system libraries for anything that Vulkan heavily relies on (such as WSI functionality), and use artifact libraries for the rest.

It may however happen that you depend on the same library for both Vulkan and artifact functionality: for example, let's say you use [GLFW.jl](https://github.com/JuliaGL/GLFW.jl), which depends on the artifact `GLFW_jll`, and you are using it with Vulkan. The Vulkan loader (usually a system library itself, `libvulkan.so`) will expect a system `libxcb.so`; and `GLFW_jll` will be designed to work with the artifact `libxcb.so`. In theory, it is possible to use different versions of the same library at the same time (see [Overriding libraries](@ref)); if it works, it's probably alright to stick with that. Otherwise, should any issue occur by this mismatch, it might be preferable to use the newest library among both, or decide on a case-by-case basis. Any battle-tested guideline for this would be very welcome!

If you stumble upon an error during instance creation and wonder if it's related to library compatibility issues, these tend to show up when the `VK_LOADER_DEBUG=all` option is set; see [Internal API errors](@ref).

## Overriding libraries

**Vulkan** may be redirected to use a specific system or artifact library. It can be attempted by:
- Forcing the system linker to preload a specific library (e.g. `LD_PRELOAD` for `ld` on linux).
- Emulating such preload using `Libdl.dlopen` before the corresponding library is loaded; that is, before `using Package` where `Package` depends on artifacts (artifacts tend to `dlopen` their library dependencies during [module initialization](https://docs.julialang.org/en/v1/manual/modules/#Module-initialization-and-precompilation)).
- Loading an artifact (either directly or indirectly), triggering the loading of its dependent libraries (which may be redirected too, see below).

**Artifacts** always use artifact libraries by default, but may be redirected toward other libraries via the preferences mechanism:

```julia-repl
julia> using Xorg_libxcb_jll
julia> Xorg_libxcb_jll.set_preferences!(Xorg_libxcb_jll, "libxcb_path" => "/usr/lib/libxcb.so")
# Restart Julia to trigger precompilation, updating artifact settings.
julia> using Xorg_libxcb_jll
```

Note that every artifact may provide many library products, and each one of them will require an explicit preference to opt out of the artifact system. For instance, `Xorg_libxcb_jll` provides `libxcb.so`, but also `libxcb-render.so`, `libxcb-xkb.so`, and many more; `libxcb_path` only affects `libxcb.so`, and to affect these other libraries there exist similar preferences `libxcb_render_path`, `libxcb_xkb_path`, etc.
2 changes: 1 addition & 1 deletion docs/src/glossary.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Glossary

*Core handle*: Opaque pointer (`void*`) extensively used by the Vulkan API. See the [Object model](https://www.khronos.org/registry/vulkan/specs/1.3/html/vkspec.html#fundamentals-objectmodel-overview) section of the Vulkan documentation for more details.
*Core handle*: Opaque pointer (`void*`) extensively used by the Vulkan API. See the [Object model](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap3.html#fundamentals-objectmodel-overview) section of the Vulkan documentation for more details.

*Handle*: Mutable type which wraps a core handle, allowing the use of finalizers to call API destructors with a reference counting mechanism to ensure no handle is destroyed before its children. Read more about them [here](@ref Handles).

Expand Down
4 changes: 2 additions & 2 deletions docs/src/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ If using VSCode, you can set it for the integrated terminal (e.g. `terminal.inte

## Internal API errors

If you encounter the error `INITIALIZATION_FAILED` or similar errors with Julia, which you do not encounter with other languages (e.g. C/C++) or with your system Vulkan utilities, then it may be due to `libstdc++` version requirements (see [this tip](@ref libstdc)).
If you encounter the error `INITIALIZATION_FAILED` or similar errors with Julia, which you do not encounter with other languages (e.g. C/C++) or with your system Vulkan utilities, then it may be due to `libstdc++` version requirements (see [this tip](@ref libstdc)) or [incompatibilities in library loading](@ref Library-loading).

If the bug is encountered in a function from the loader (e.g. via a function that operates on an `Instance`, and not a `Device`), and you use the official [Vulkan-Loader](https://github.com/KhronosGroup/Vulkan-Loader) you can turn on logging via setting the environment variable `VK_LOADER_DEBUG=all`. This should help you understand the cause (see [Debug environment variables](https://github.com/KhronosGroup/Vulkan-Loader/blob/master/docs/LoaderInterfaceArchitecture.md#table-of-debug-environment-variables=) for more options).
If the bug is encountered in a function from the loader (e.g. via a function that operates on an `Instance`, and not a `Device`), and if you are using [Vulkan-Loader](https://github.com/KhronosGroup/Vulkan-Loader) (which is most likely the case), it is recommended to enable additional logging by setting the environment variable `VK_LOADER_DEBUG=all`. See [the loader's debug environment variables](https://github.com/KhronosGroup/Vulkan-Loader/blob/master/docs/LoaderInterfaceArchitecture.md#table-of-debug-environment-variables) for more options.

## 0-based vs 1-based indexing

Expand Down
2 changes: 2 additions & 0 deletions docs/src/tutorial/indepth.jl
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,6 @@ const debug_messenger = DebugUtilsMessengerEXT(
We can now enumerate and pick a physical device that we will use for this tutorial.
*Work in progress.*
=#
2 changes: 1 addition & 1 deletion docs/src/tutorial/minimal_working_compute.jl
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ unwrap(queue_submit(compute_q, [SubmitInfo([], [], [cbuf], [])]))
# that for example the pipeline and buffer objects are still used and that
# there's a dependency with these variables until the command returns, so we
# tell it manually.
GC.@preserve buff dsl pl p const_buf spec_consts begin
GC.@preserve buffer dsl pl p const_buf spec_consts begin
unwrap(queue_wait_idle(compute_q))
end

Expand Down
8 changes: 5 additions & 3 deletions docs/src/utility.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ Feel free to check out the official [Vulkan website](https://www.vulkan.org/) fo

### NVIDIA Nsight Systems

[NVIDIA Nsight Systems](https://developer.nvidia.com/nsight-systems) is a tool developed by NVIDIA to profile applications, showing both CPU and GPU usage. It can be very useful for analyzing the balance between CPU and GPU usage, as well as troubleshoot general performance bottlenecks. However, it only outputs high-level information regarding GPU tasks. Therefore, to catch GPU bottlenecks on a low-level (such as inside shaders) one should instead use a dedicated profiler such as [Nsight Graphics](@ref nsight-graphics) or [Renderdoc](@ref renderdoc).
[NVIDIA Nsight Systems](https://developer.nvidia.com/nsight-systems) is a tool developed by NVIDIA to profile applications, showing both CPU and GPU usage. It can be very useful for analyzing the balance between CPU and GPU usage, as well as troubleshoot general performance bottlenecks. However, it only outputs high-level information regarding GPU tasks. Therefore, to catch GPU bottlenecks in a fine-grained manner (such as inside shaders) one should instead use a dedicated profiler such as [Nsight Graphics](@ref nsight-graphics).

### [NVIDIA Nsight Graphics](@id nsight-graphics)

[Nsight Graphics](https://developer.nvidia.com/nsight-graphics) dives deeper into the execution details of an application and provides detailed information regarding graphics pipelines, shaders and so on. This is a tool of choice to consider for NVIDIA GPUs once the GPU is identified as a bottleneck with Nsight Systems.

### [Renderdoc](@id renderdoc)
### RenderDoc

[Renderdoc](https://renderdoc.org/) plays a similar role to Nsight Graphics for a wider range of GPUs. It is open-source and community-maintained.
[RenderDoc](https://renderdoc.org/) plays a similar role to Nsight Graphics for a wider range of GPUs. It is open-source and community-maintained.

*RenderDoc is not supported with Vulkan.jl; see [this issue](https://github.com/JuliaGPU/Vulkan.jl/issues/53) for more details on the matter.*

### CPU implementation of Vulkan

Expand Down
Empty file removed generator/src/wrapper.jl
Empty file.
4 changes: 2 additions & 2 deletions src/driver.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ function set_driver(backend::Symbol)
drivers = [icd_filenames; driver_files]
swiftshader_icd = joinpath(libdir, "vk_swiftshader_icd.json")
!in(swiftshader_icd, drivers) && push!(drivers, swiftshader_icd)
ENV["VK_ICD_FILENAMES"] = join(icd_filenames, sep)
ENV["VK_DRIVER_FILES"] = join(icd_filenames, sep)
ENV["VK_ICD_FILENAMES"] = join(drivers, sep)
ENV["VK_DRIVER_FILES"] = join(drivers, sep)
end
_ => error("Backend `$backend` not available. Only 'SwiftShader' is currently supported.")
end
Expand Down
1 change: 1 addition & 0 deletions src/preferences.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Preferences: Preferences, @load_preference
set_preferences!(args...; kwargs...) = Preferences.set_preferences!(@__MODULE__, args...; kwargs...)
load_preference(args...; kwargs...) = Preferences.load_preference(@__MODULE__, args...; kwargs...)

macro pref_log_destruction(handle, ex)
if @load_preference("LOG_DESTRUCTION", "false") == "true"
Expand Down
41 changes: 41 additions & 0 deletions src/prewrap/handles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ function try_destroy(f, handle::Handle, parent)
if !isnothing(parent) && !isa(parent.destructor, UndefInitializer)
parent.destructor()
end
return true
end
handle.refcount[]
end

function init_handle!(handle::Handle, destructor, parent=nothing)
Expand All @@ -46,6 +48,45 @@ function (T::Type{<:Handle})(ptr::Ptr{Cvoid}, destructor, parent)
init_handle!(T(ptr, parent, RefCounter(UInt(1))), destructor, parent)
end

"""
depends_on(x, handle::Handle)
Make reference counting aware that `x` depends on `handle`.
This ensures that `handle` is destroyed *after* `x`, and not the other way around.
This may notably be used to encode dependencies that fall out of Vulkan's handle hierarchy,
such as between a `SurfaceKHR` and a `SwapchainKHR`.
If `x` is not a `Handle`, it must be a mutable object; in this case, a finalizer will be added
which decrements the `handle`'s reference count (and destroys them if it reaches zero).
`depends_on(x, handle)` is idempotent: multiple calls to it will simply incur needless incrementing/decrementing and finalizer registrations, possibly harming performance, but will not cause bugs.
If one is a parent handle of the other (i.e. `Vk.parent(x) === handle`), `depends_on(x, handle)` is already implicit, and needs not be used.
!!! warning
`depends_on` must not be used in a circular manner: using both `depends_on(x, y)` and `depends_on(y, x)` will prevent both `x` and `y` from ever being destroyed. Same for `depends_on(x, y)`, `depends_on(y, z)`, `depends_on(z, x)` and so on.
"""
function depends_on end

function depends_on(x::Vk.Handle, handle::Vk.Handle)
Vk.increment_refcount!(handle)
prev_destructor = x.destructor
x.destructor = () -> begin
prev_destructor()
iszero(x.refcount[]) && handle.destructor()
end
nothing
end

function depends_on(x, handle::Vk.Handle)
T = typeof(x)
ismutabletype(T) || error("`x` must be a mutable object or a `Vk.Handle`")
finalizer(_ -> handle.destructor(), x)
nothing
end

macro dispatch(handle, expr)
if @load_preference("USE_DISPATCH_TABLE", "true") == "true"
@match expr begin
Expand Down
2 changes: 1 addition & 1 deletion test/api.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const WITH_DEBUG = let available_extensions = unwrap(enumerate_instance_extensio
end
end

@testset "Vulkan tests" begin
@testset "Vulkan API usage" begin
include("init.jl")

@testset "Utilities" begin
Expand Down
Loading

0 comments on commit db421fe

Please sign in to comment.