├── .gitignore ├── .simplecov ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console ├── setup └── setup.bat ├── examples ├── models │ └── viking_room.obj ├── screenshots │ └── vt10-viking_room.png ├── shaders │ ├── depth_buffering.vert │ ├── depth_buffering.vert.spv │ ├── model_loading.frag │ ├── model_loading.frag.spv │ ├── model_loading.vert │ ├── model_loading.vert.spv │ ├── textures.frag │ ├── textures.frag.spv │ ├── textures.vert │ ├── textures.vert.spv │ ├── triangle.frag │ ├── triangle.frag.spv │ ├── triangle.vert │ ├── triangle.vert.spv │ ├── uniform_buffer.vert │ ├── uniform_buffer.vert.spv │ ├── vertex_buffer.vert │ └── vertex_buffer.vert.spv ├── textures │ ├── texture.png │ └── viking_room.png ├── vt00_dump_info.rb ├── vt01_triangle.rb ├── vt02_vertex_buffer_triangle_host_visible.rb ├── vt03_vertex_buffer_triangle_device_local.rb ├── vt04_index_buffer_device_local.rb ├── vt05_uniform_buffers.rb ├── vt06_textures.rb ├── vt07_depth_buffering.rb ├── vt08_model_loading.rb ├── vt09_mipmaps.rb └── vt10_multisampling.rb ├── lib ├── fiddle_ext.rb ├── vulkan-ruby.rb ├── vulkan.rb └── vulkan │ ├── buffer.rb │ ├── buffer_memory.rb │ ├── buffer_memory_barrier.rb │ ├── checks.rb │ ├── command_buffer.rb │ ├── command_pool.rb │ ├── conversions.rb │ ├── descriptor_pool.rb │ ├── descriptor_set.rb │ ├── descriptor_set_layout.rb │ ├── dispatch_table.rb │ ├── error.rb │ ├── fence.rb │ ├── finalizer.rb │ ├── framebuffer.rb │ ├── generated.rb │ ├── generated │ ├── commands.rb │ ├── enums.rb │ ├── structs.rb │ ├── types.rb │ ├── version.rb │ └── vk.xml │ ├── image.rb │ ├── image_memory.rb │ ├── image_view.rb │ ├── instance.rb │ ├── logical_device.rb │ ├── manual_types.rb │ ├── memory.rb │ ├── memory_barrier.rb │ ├── mock.rb │ ├── mock │ └── swapchain_surface_info.rb │ ├── physical_device.rb │ ├── pipeline.rb │ ├── platform.rb │ ├── queue.rb │ ├── queue_family.rb │ ├── render_pass.rb │ ├── render_pass │ └── subpass.rb │ ├── sampler.rb │ ├── semaphore.rb │ ├── shader_stage.rb │ ├── surface.rb │ ├── swapchain.rb │ ├── swapchain_builder.rb │ ├── swapchain_surface_info.rb │ ├── version.rb │ └── window_surface.rb ├── tasks ├── examples.rake ├── fetch.rake ├── generate.rake ├── generate │ ├── commands.rake │ ├── enums.rake │ ├── extensions.rake │ ├── structs.rake │ ├── types.rake │ └── version.rake ├── helpers.rb └── shaders.rake ├── test ├── barriers_test.rb ├── conversions_test.rb ├── devices_test.rb ├── dispatch_table_test.rb ├── generators_test.rb ├── logical_device_test.rb ├── pipelines_test.rb ├── semaphores_test.rb ├── struct_test.rb ├── swapchain_builder_test.rb ├── test_helper.rb └── vulkan_test.rb └── vulkan-ruby.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | /ports 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /.simplecov: -------------------------------------------------------------------------------- 1 | SimpleCov.add_filter 'vulkan/generated/' 2 | 3 | if ENV['COVERAGE'] 4 | SimpleCov.command_name ENV['COVERAGE'] 5 | SimpleCov.start 6 | end 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 3.1.1 5 | before_install: gem install bundler -v 2.2.0.rc.1 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 4 | 5 | # Specify your gem's dependencies in vulkan-ruby.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | vulkan-ruby (1.3.207.5) 5 | sorted_set 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | cglm (0.1.1) 11 | chunky_png (1.3.12) 12 | docile (1.3.2) 13 | mini_portile2 (2.8.0) 14 | minitest (5.15.0) 15 | nokogiri (1.13.3) 16 | mini_portile2 (~> 2.8.0) 17 | racc (~> 1.4) 18 | oily_png (1.2.1) 19 | chunky_png (~> 1.3.7) 20 | racc (1.6.0) 21 | rake (13.0.1) 22 | rbtree (0.4.5) 23 | ruby-sdl2 (0.3.5) 24 | sdl2_vulkan (0.1.0) 25 | ruby-sdl2 (~> 0.3) 26 | set (1.0.2) 27 | simplecov (0.19.0) 28 | docile (~> 1.1) 29 | simplecov-html (~> 0.11) 30 | simplecov-html (0.12.3) 31 | sorted_set (1.0.3) 32 | rbtree 33 | set (~> 1.0) 34 | tiny_obj (0.2.5) 35 | 36 | PLATFORMS 37 | ruby 38 | 39 | DEPENDENCIES 40 | bundler (~> 2.1) 41 | cglm (~> 0.1) 42 | chunky_png (~> 1.3) 43 | minitest (~> 5.15) 44 | nokogiri (~> 1.8) 45 | oily_png (~> 1.2) 46 | rake (~> 13.0) 47 | sdl2_vulkan (~> 0.1) 48 | simplecov (~> 0.16) 49 | tiny_obj (~> 0.2) 50 | vulkan-ruby! 51 | 52 | BUNDLED WITH 53 | 2.5.17 54 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Colin MacKenzie IV 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `vulkan-ruby` 2 | 3 | Exposes [Vulkan](https://www.khronos.org/vulkan/), the next-generation graphics library, to Ruby! 4 | 5 | **NOTICE**: Vulkan is neither a small nor simple API to implement. While this library is rapidly reaching a point of usability, it's quite possible you will find that it is insufficient in some way. You are encouraged to open new issues for missing functionality that should otherwise be possible with Vulkan (bonus points for pull requests). The rule of thumb is that if it can be done with Vulkan in C, it should be equally doable using this library. 6 | 7 | Higher level abstractions must be evaluated on a case by case basis and are not guaranteed to be added. For example, something explicitly specified in the Vulkan spec such as `vkCmdBindPipeline` or an official extension such as `vk_nv_external_memory` should definitely work in `vulkan-ruby` and support would absolutely be added for them if they don't already work. An implementation of cascading shadow maps, on the other hand, would be better implemented as a standalone ruby gem and at least at the time of this writing would likely be rejected unless a very good explanation could be given for having it. 8 | 9 | ## Screenshots 10 | 11 | ![VT10 - Multisampling - Viking Room](https://github.com/sinisterchipmunk/vulkan-ruby/blob/master/examples/screenshots/vt10-viking_room.png) 12 | 13 | 14 | ## Installation 15 | 16 | Add this line to your application's Gemfile: 17 | 18 | ```ruby 19 | gem 'vulkan-ruby' 20 | ``` 21 | 22 | And then execute: 23 | 24 | $ bundle 25 | 26 | Or install it yourself as: 27 | 28 | $ gem install vulkan-ruby 29 | 30 | 31 | ## A Note About Versioning 32 | 33 | The first 3 components of the version number of this gem represent the version 34 | of the Vulkan spec that the gem was built with. For example, 1.3.207.2 would 35 | represent the second (presumably bug-fix) release of this gem built against 36 | version 1.3.207 of the Vulkan spec. 37 | 38 | 39 | ## Usage 40 | 41 | This library is intended to be a rather shallow wrapper around the Vulkan APIs. It does some things that are nifty (like tying garbage collection of Vulkan resources to the Ruby garbage collector), but mostly the design goal is to give you an interface into Vulkan without getting in the way. As with Vulkan itself, some things are possible to do in a number of different ways, depending on your specific use case. Nothing that is possible in Vulkan should prove _impossible_ to do with `vulkan-ruby`. 42 | 43 | You are encouraged to [check out the examples](https://github.com/sinisterchipmunk/vulkan-ruby/tree/master/examples/) for some specific usage examples. 44 | 45 | ### Extensions and Layers 46 | 47 | If the environment variable `DEBUG` is set, the following extensions and layers will be added (only if supported by the underlying implementation): 48 | 49 | * Instance Extensions 50 | 51 | ``` 52 | VK_EXT_debug_utils 53 | ``` 54 | 55 | * Layers 56 | 57 | ``` 58 | VK_LAYER_GOOGLE_threading 59 | VK_LAYER_GOOGLE_unique_objects 60 | VK_LAYER_LUNARG_api_dump 61 | VK_LAYER_LUNARG_assistant_layer 62 | VK_LAYER_LUNARG_core_validation 63 | VK_LAYER_LUNARG_demo_layer 64 | VK_LAYER_LUNARG_monitor 65 | VK_LAYER_LUNARG_object_tracker 66 | VK_LAYER_LUNARG_parameter_validation 67 | VK_LAYER_LUNARG_standard_validation 68 | VK_LAYER_LUNARG_starter_layer 69 | ``` 70 | 71 | In addition to the usual mechanisms for explicit and implicit Vulkan extensions, you can also provide the following environment variables to list extensions/layers to be activated. These get injected into the lists provided by the application to Vulkan, rather than interpreted by Vulkan itself. Multiple entries can be separated by spaces (` `) or by colons (`:`). 72 | 73 | LAYERS 74 | INSTANCE_EXTENSIONS 75 | DEVICE_EXTENSIONS 76 | 77 | 78 | ## Development 79 | 80 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 81 | 82 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 83 | 84 | When running `rake`, you can pass `PATH_TO_VULKAN` to specify the Vulkan library to load during testing. Otherwise `NULL` will be passed into SDL2, indicating that it should search for the library. 85 | 86 | Everything in `lib/vulkan/generated/` was automatically generated by processing the Vulkan `vk.xml` spec file. To (re)generate these files, first fetch `vk.xml` with `rake fetch`, and then generate the files with `rake generate`, or do it all in one command like so: 87 | 88 | ```bash 89 | $ bundle exec rake fetch generate 90 | ``` 91 | 92 | After (re)generating the files, run `rake` with no arguments, which will invoke both the unit tests and the example files, to (a) check that nothing was broken by the new files, and (b) check that the generated files are correct. 93 | 94 | If you discover that the generated files are not correct (for instance, lacking some structs or enums or some generated results just have wrong values), write a unit test to assert the expected result before modifying the generation Rake tasks in `tasks/**/*.rake`. Or at least write the test once you get the result you're looking for. The point is, since these rake tasks are both complicated and mission-critical, unit tests need to be written to be sure that there are no regressions as they evolve over time. 95 | 96 | ## Contributing 97 | 98 | Bug reports and pull requests are welcome on GitHub at https://github.com/sinisterchipmunk/vulkan-ruby. 99 | 100 | ## License 101 | 102 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 103 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test" 6 | t.libs << "lib" 7 | t.test_files = FileList["test/**/*_test.rb"] 8 | end 9 | 10 | Dir[File.expand_path('tasks/**/*.{rake,rb}', __dir__)].each do |file| 11 | load File.expand_path(file, __dir__) 12 | end 13 | 14 | task :default => :test 15 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "vulkan/ruby" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | bundle exec ruby bin/build-fiddle.rb 10 | -------------------------------------------------------------------------------- /bin/setup.bat: -------------------------------------------------------------------------------- 1 | call bundle install 2 | call bundle exec ruby bin\build-fiddle.rb 3 | -------------------------------------------------------------------------------- /examples/screenshots/vt10-viking_room.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinisterchipmunk/vulkan-ruby/593aad74a9acb8efc9c41c1d65eab6764a8920aa/examples/screenshots/vt10-viking_room.png -------------------------------------------------------------------------------- /examples/shaders/depth_buffering.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 0) uniform UniformBufferObject { 5 | mat4 model; 6 | mat4 view; 7 | mat4 proj; 8 | } ubo; 9 | 10 | layout(location = 0) in vec3 inPosition; 11 | layout(location = 1) in vec3 inColor; 12 | layout(location = 2) in vec2 inTexCoord; 13 | 14 | layout(location = 0) out vec3 fragColor; 15 | layout(location = 1) out vec2 fragTexCoord; 16 | 17 | out gl_PerVertex { 18 | vec4 gl_Position; 19 | }; 20 | 21 | void main() { 22 | gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); 23 | fragColor = inColor; 24 | fragTexCoord = inTexCoord; 25 | } 26 | -------------------------------------------------------------------------------- /examples/shaders/depth_buffering.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinisterchipmunk/vulkan-ruby/593aad74a9acb8efc9c41c1d65eab6764a8920aa/examples/shaders/depth_buffering.vert.spv -------------------------------------------------------------------------------- /examples/shaders/model_loading.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 1) uniform sampler2D texSampler; 5 | 6 | layout(location = 0) in vec3 fragColor; 7 | layout(location = 1) in vec2 fragTexCoord; 8 | 9 | layout(location = 0) out vec4 outColor; 10 | 11 | void main() { 12 | outColor = vec4(fragColor * texture(texSampler, fragTexCoord).rgb, 1.0); 13 | } 14 | -------------------------------------------------------------------------------- /examples/shaders/model_loading.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinisterchipmunk/vulkan-ruby/593aad74a9acb8efc9c41c1d65eab6764a8920aa/examples/shaders/model_loading.frag.spv -------------------------------------------------------------------------------- /examples/shaders/model_loading.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 0) uniform UniformBufferObject { 5 | mat4 model; 6 | mat4 view; 7 | mat4 proj; 8 | } ubo; 9 | 10 | layout(location = 0) in vec3 inPosition; 11 | layout(location = 1) in vec3 inColor; 12 | layout(location = 2) in vec2 inTexCoord; 13 | 14 | layout(location = 0) out vec3 fragColor; 15 | layout(location = 1) out vec2 fragTexCoord; 16 | 17 | out gl_PerVertex { 18 | vec4 gl_Position; 19 | }; 20 | 21 | void main() { 22 | gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0); 23 | fragColor = inColor; 24 | fragTexCoord = inTexCoord; 25 | } 26 | -------------------------------------------------------------------------------- /examples/shaders/model_loading.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinisterchipmunk/vulkan-ruby/593aad74a9acb8efc9c41c1d65eab6764a8920aa/examples/shaders/model_loading.vert.spv -------------------------------------------------------------------------------- /examples/shaders/textures.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 1) uniform sampler2D texSampler; 5 | 6 | layout(location = 0) in vec3 fragColor; 7 | layout(location = 1) in vec2 fragTexCoord; 8 | 9 | layout(location = 0) out vec4 outColor; 10 | 11 | void main() { 12 | outColor = vec4(fragColor * texture(texSampler, fragTexCoord * 2.0).rgb, 1.0); 13 | } 14 | -------------------------------------------------------------------------------- /examples/shaders/textures.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinisterchipmunk/vulkan-ruby/593aad74a9acb8efc9c41c1d65eab6764a8920aa/examples/shaders/textures.frag.spv -------------------------------------------------------------------------------- /examples/shaders/textures.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 0) uniform UniformBufferObject { 5 | mat4 model; 6 | mat4 view; 7 | mat4 proj; 8 | } ubo; 9 | 10 | layout(location = 0) in vec2 inPosition; 11 | layout(location = 1) in vec3 inColor; 12 | layout(location = 2) in vec2 inTexCoord; 13 | 14 | layout(location = 0) out vec3 fragColor; 15 | layout(location = 1) out vec2 fragTexCoord; 16 | 17 | out gl_PerVertex { 18 | vec4 gl_Position; 19 | }; 20 | 21 | void main() { 22 | gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); 23 | fragColor = inColor; 24 | fragTexCoord = inTexCoord; 25 | } 26 | -------------------------------------------------------------------------------- /examples/shaders/textures.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinisterchipmunk/vulkan-ruby/593aad74a9acb8efc9c41c1d65eab6764a8920aa/examples/shaders/textures.vert.spv -------------------------------------------------------------------------------- /examples/shaders/triangle.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(location = 0) in vec3 fragColor; 5 | 6 | layout(location = 0) out vec4 outColor; 7 | 8 | void main() { 9 | outColor = vec4(fragColor, 1.0); 10 | } 11 | -------------------------------------------------------------------------------- /examples/shaders/triangle.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinisterchipmunk/vulkan-ruby/593aad74a9acb8efc9c41c1d65eab6764a8920aa/examples/shaders/triangle.frag.spv -------------------------------------------------------------------------------- /examples/shaders/triangle.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | out gl_PerVertex { 5 | vec4 gl_Position; 6 | }; 7 | 8 | layout(location = 0) out vec3 fragColor; 9 | 10 | vec2 positions[3] = vec2[]( 11 | vec2(0.0, -0.5), 12 | vec2(-0.5, 0.5), 13 | vec2(0.5, 0.5) 14 | ); 15 | 16 | vec3 colors[3] = vec3[]( 17 | vec3(1.0, 0.0, 0.0), 18 | vec3(0.0, 1.0, 0.0), 19 | vec3(0.0, 0.0, 1.0) 20 | ); 21 | 22 | void main() { 23 | gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); 24 | fragColor = colors[gl_VertexIndex]; 25 | } 26 | -------------------------------------------------------------------------------- /examples/shaders/triangle.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinisterchipmunk/vulkan-ruby/593aad74a9acb8efc9c41c1d65eab6764a8920aa/examples/shaders/triangle.vert.spv -------------------------------------------------------------------------------- /examples/shaders/uniform_buffer.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 0) uniform UniformBufferObject { 5 | mat4 model; 6 | mat4 view; 7 | mat4 proj; 8 | } ubo; 9 | 10 | layout(location = 0) in vec2 inPosition; 11 | layout(location = 1) in vec3 inColor; 12 | 13 | layout(location = 0) out vec3 fragColor; 14 | 15 | out gl_PerVertex { 16 | vec4 gl_Position; 17 | }; 18 | 19 | void main() { 20 | gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0); 21 | fragColor = inColor; 22 | } 23 | -------------------------------------------------------------------------------- /examples/shaders/uniform_buffer.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinisterchipmunk/vulkan-ruby/593aad74a9acb8efc9c41c1d65eab6764a8920aa/examples/shaders/uniform_buffer.vert.spv -------------------------------------------------------------------------------- /examples/shaders/vertex_buffer.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(location = 0) in vec2 inPosition; 5 | layout(location = 1) in vec3 inColor; 6 | 7 | layout(location = 0) out vec3 fragColor; 8 | 9 | out gl_PerVertex { 10 | vec4 gl_Position; 11 | }; 12 | 13 | void main() { 14 | gl_Position = vec4(inPosition, 0.0, 1.0); 15 | fragColor = inColor; 16 | } 17 | -------------------------------------------------------------------------------- /examples/shaders/vertex_buffer.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinisterchipmunk/vulkan-ruby/593aad74a9acb8efc9c41c1d65eab6764a8920aa/examples/shaders/vertex_buffer.vert.spv -------------------------------------------------------------------------------- /examples/textures/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinisterchipmunk/vulkan-ruby/593aad74a9acb8efc9c41c1d65eab6764a8920aa/examples/textures/texture.png -------------------------------------------------------------------------------- /examples/textures/viking_room.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinisterchipmunk/vulkan-ruby/593aad74a9acb8efc9c41c1d65eab6764a8920aa/examples/textures/viking_room.png -------------------------------------------------------------------------------- /examples/vt00_dump_info.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'simplecov' 3 | require 'vulkan' 4 | require 'pp' 5 | 6 | puts 'SUPPORTED EXTENSIONS' 7 | puts '--------------------' 8 | puts 9 | pp Vulkan::Instance.extensions 10 | puts 11 | puts 12 | 13 | puts 'SUPPORTED LAYERS' 14 | puts '--------------------' 15 | puts 16 | pp Vulkan::Instance.layers 17 | puts 18 | puts 19 | 20 | puts 'PHYSICAL DEVICES' 21 | puts '----------------' 22 | puts 23 | pp Vulkan::Instance.new(extensions: []).physical_devices.map { |dev| dev.to_hash } 24 | puts 25 | -------------------------------------------------------------------------------- /examples/vt01_triangle.rb: -------------------------------------------------------------------------------- 1 | # https://vulkan-tutorial.com/code/16_swap_chain_recreation.cpp 2 | 3 | require 'bundler/setup' 4 | require 'simplecov' 5 | require 'vulkan' 6 | require 'sdl2_vulkan' 7 | 8 | # Create a window that we plan to draw to 9 | SDL2.init(SDL2::INIT_EVERYTHING) 10 | window = SDL2::Window.create "Hello Triangle", 0, 0, 640, 480, 11 | SDL2::Window::Flags::VULKAN | 12 | SDL2::Window::Flags::RESIZABLE | 13 | 0x00002000 # SDL2::Window::Flags::ALLOW_HIGHDPI 14 | 15 | # Create a Vulkan instance 16 | instance = Vulkan::Instance.new extensions: window.vk_instance_extensions 17 | 18 | # Create a rendering surface 19 | surface = instance.create_window_surface(window) 20 | 21 | # Choose an adequate physical device 22 | dev = instance.physical_devices.detect do |dev| 23 | next false unless swapchain_surface_info = dev.swapchain_surface_info(surface) 24 | builder = Vulkan::SwapchainBuilder.new(swapchain_surface_info) 25 | dev.properties[:device_type] == :discrete_gpu && 26 | (builder.format rescue false) && (builder.presentation_mode rescue false) 27 | end 28 | raise 'could not find a suitable physical device' unless dev 29 | 30 | # Find a queue family that supports graphics 31 | graphics_queue_family = dev.queue_families.detect do |family| 32 | family[:queue_count] > 0 && family.supports?(:graphics) 33 | end 34 | raise 'no graphics queue family available' unless graphics_queue_family 35 | 36 | # Find a queue family that supports presentation to the rendering surface 37 | presentation_queue_family = dev.queue_families.detect do |family| 38 | family[:queue_count] > 0 && family.supports_presentation?(surface) 39 | end 40 | raise 'no graphics queue family available' unless presentation_queue_family 41 | 42 | # Create a logical device along with the needed extensions and chosen queues 43 | if graphics_queue_family == presentation_queue_family 44 | device = dev.create queues: [{ family: graphics_queue_family, priorities: [1.0] }], 45 | extensions: ['VK_KHR_swapchain'] 46 | presentation_queue = graphics_queue = device.queue_families[0][:queues][0] 47 | else 48 | device = dev.create queues: [{ family: graphics_queue_family, priorities: [1.0] }, 49 | { family: presentation_queue_family, priorities: [1.0] }], 50 | extensions: ['VK_KHR_swapchain'] 51 | graphics_queue = device.queue_families[0][:queues][0] 52 | presentation_queue = device.queue_families[1][:queues][0] 53 | end 54 | 55 | command_pool = device.create_command_pool queue_family: graphics_queue_family 56 | 57 | shader_stages = [ 58 | device.create_shader_stage(file_path: File.expand_path('./shaders/triangle.vert.spv', __dir__), 59 | stage: Vulkan::VK_SHADER_STAGE_VERTEX_BIT), 60 | device.create_shader_stage(file_path: File.expand_path('./shaders/triangle.frag.spv', __dir__), 61 | stage: Vulkan::VK_SHADER_STAGE_FRAGMENT_BIT) 62 | ] 63 | 64 | rebuild_swap_chain = proc do 65 | # Create a swapchain for image presentation 66 | $swapchain = device.create_swapchain surface: surface, 67 | surface_width: window.vk_drawable_size[0], 68 | surface_height: window.vk_drawable_size[1] 69 | 70 | # Create render pass with one subpass 71 | render_pass = device.create_renderpass 72 | subpass = render_pass.add_subpass 73 | subpass.add_color_attachment_ref index: render_pass.add_attachment(format: $swapchain.format), 74 | layout: :color 75 | render_pass.commit 76 | 77 | # Create a framebuffer for each image in the swap chain 78 | framebuffers = $swapchain.image_views.map do |swapchain_image_view| 79 | device.create_framebuffer(width: $swapchain.width, 80 | height: $swapchain.height, 81 | render_pass: render_pass, 82 | attachments: [swapchain_image_view]) 83 | end 84 | 85 | # Create graphic pipeline 86 | pipeline = device.create_pipeline(viewport: { width: $swapchain.width, height: $swapchain.height }) 87 | shader_stages.each { |stage| pipeline.add_shader_stage(stage) } 88 | pipeline.commit(render_pass) 89 | 90 | $command_buffers = command_pool.create_command_buffers(framebuffers.size) do |cmd, index| 91 | cmd.render_pass(render_pass, framebuffer: framebuffers[index]) do 92 | cmd.bind_pipeline(:graphics, pipeline) 93 | cmd.draw(3, 1, 0, 0) 94 | end 95 | end 96 | end 97 | 98 | rebuild_swap_chain.call 99 | 100 | MAX_FRAMES_IN_FLIGHT = 2 101 | image_available_semaphores = MAX_FRAMES_IN_FLIGHT.times.map { device.create_semaphore } 102 | render_finished_semaphores = MAX_FRAMES_IN_FLIGHT.times.map { device.create_semaphore } 103 | in_flight_fences = MAX_FRAMES_IN_FLIGHT.times.map { device.create_fence signaled: true } 104 | current_frame = 0 105 | frame_counter = 0 106 | done = false 107 | 108 | # Begin main event loop & drawing 109 | until done 110 | frame_counter += 1 111 | break if ENV['MAX_FRAMES'].to_i == frame_counter 112 | while event = SDL2::Event.poll 113 | case event 114 | when SDL2::Event::Quit, SDL2::Event::KeyDown then done = true 115 | when SDL2::Event::Window::RESIZED then rebuild_swap_chain.call 116 | end 117 | end 118 | 119 | begin 120 | in_flight_fences[current_frame].wait_and_reset 121 | image_index = $swapchain.next_image_index(semaphore: image_available_semaphores[current_frame]) 122 | graphics_queue.submit([$command_buffers[image_index]], 123 | wait_semaphores: [image_available_semaphores[current_frame]], 124 | wait_stages: [Vulkan::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT], 125 | signal_semaphores: [render_finished_semaphores[current_frame]], 126 | fence: in_flight_fences[current_frame]) 127 | presentation_queue.present(swapchains: [$swapchain], 128 | image_indices: [image_index], 129 | wait_semaphores: [render_finished_semaphores[current_frame]]) 130 | current_frame = (current_frame + 1) % MAX_FRAMES_IN_FLIGHT 131 | rescue Vulkan::Error 132 | case $!.code 133 | when Vulkan::VK_ERROR_OUT_OF_DATE_KHR, Vulkan::VK_SUBOPTIMAL_KHR 134 | rebuild_swap_chain.call 135 | else raise 136 | end 137 | end 138 | end 139 | 140 | in_flight_fences.each { |fence| fence.wait_and_reset } 141 | -------------------------------------------------------------------------------- /examples/vt02_vertex_buffer_triangle_host_visible.rb: -------------------------------------------------------------------------------- 1 | # https://vulkan-tutorial.com/code/18_vertex_buffer.cpp 2 | 3 | require 'bundler/setup' 4 | require 'simplecov' 5 | require 'vulkan' 6 | require 'sdl2_vulkan' 7 | 8 | Vertex = Vulkan.struct(['float pos[2]', 'float color[3]']) 9 | VertexData = Vulkan.struct('vertices[3]' => Vertex).malloc 10 | VertexData.vertices[0].pos = [ 0, -0.5]; VertexData.vertices[0].color = [1, 0, 0] 11 | VertexData.vertices[1].pos = [-0.5, 0.5]; VertexData.vertices[1].color = [0, 1, 0] 12 | VertexData.vertices[2].pos = [ 0.5, 0.5]; VertexData.vertices[2].color = [0, 0, 1] 13 | 14 | # Create a window that we plan to draw to 15 | SDL2.init(SDL2::INIT_EVERYTHING) 16 | window = SDL2::Window.create "test-vulkan", 0, 0, 640, 480, SDL2::Window::Flags::VULKAN | 17 | SDL2::Window::Flags::RESIZABLE | 18 | 0x00002000 # SDL2::Window::Flags::ALLOW_HIGHDPI 19 | 20 | # Create a Vulkan instance 21 | instance = Vulkan::Instance.new extensions: window.vk_instance_extensions 22 | 23 | # Create a rendering surface 24 | surface = instance.create_window_surface(window) 25 | 26 | # Choose an adequate physical device 27 | dev = instance.physical_devices.detect do |dev| 28 | next false unless swapchain_surface_info = dev.swapchain_surface_info(surface) 29 | builder = Vulkan::SwapchainBuilder.new(swapchain_surface_info) 30 | dev.properties[:device_type] == :discrete_gpu && 31 | (builder.format rescue false) && (builder.presentation_mode rescue false) 32 | end 33 | raise 'could not find a suitable physical device' unless dev 34 | 35 | # Find a queue family that supports graphics 36 | graphics_queue_family = dev.queue_families.detect do |family| 37 | family[:queue_count] > 0 && family.supports?(:graphics) 38 | end 39 | raise 'no graphics queue family available' unless graphics_queue_family 40 | 41 | # Find a queue family that supports presentation to the rendering surface 42 | presentation_queue_family = dev.queue_families.detect do |family| 43 | family[:queue_count] > 0 && family.supports_presentation?(surface) 44 | end 45 | raise 'no graphics queue family available' unless presentation_queue_family 46 | 47 | # Create a logical device along with the needed extensions and chosen queues 48 | if graphics_queue_family == presentation_queue_family 49 | device = dev.create queues: [{ family: graphics_queue_family, priorities: [1.0] }], 50 | extensions: ['VK_KHR_swapchain'] 51 | presentation_queue = graphics_queue = device.queue_families[0][:queues][0] 52 | else 53 | device = dev.create queues: [{ family: graphics_queue_family, priorities: [1.0] }, 54 | { family: presentation_queue_family, priorities: [1.0] }], 55 | extensions: ['VK_KHR_swapchain'] 56 | graphics_queue = device.queue_families[0][:queues][0] 57 | presentation_queue = device.queue_families[1][:queues][0] 58 | end 59 | 60 | VertexBuffer = device.create_buffer size: VertexData.class.size, usage: Vulkan::VK_BUFFER_USAGE_VERTEX_BUFFER_BIT 61 | VertexBuffer.map do |data| 62 | data[0, VertexData.class.size] = VertexData[0, VertexData.class.size] 63 | end 64 | 65 | command_pool = device.create_command_pool queue_family: graphics_queue_family 66 | 67 | shader_stages = [ 68 | device.create_shader_stage(file_path: File.expand_path('./shaders/vertex_buffer.vert.spv', __dir__), 69 | stage: Vulkan::VK_SHADER_STAGE_VERTEX_BIT), 70 | device.create_shader_stage(file_path: File.expand_path('./shaders/triangle.frag.spv', __dir__), 71 | stage: Vulkan::VK_SHADER_STAGE_FRAGMENT_BIT) 72 | ] 73 | 74 | rebuild_swap_chain = proc do 75 | # Create a swapchain for image presentation 76 | $swapchain = device.create_swapchain surface: surface, 77 | surface_width: window.vk_drawable_size[0], 78 | surface_height: window.vk_drawable_size[1] 79 | 80 | # Create render pass with one subpass 81 | render_pass = device.create_renderpass 82 | subpass = render_pass.add_subpass 83 | subpass.add_color_attachment_ref index: render_pass.add_attachment(format: $swapchain.format), 84 | layout: :color 85 | render_pass.commit 86 | 87 | # Create a framebuffer for each image in the swap chain 88 | framebuffers = $swapchain.image_views.map do |swapchain_image_view| 89 | device.create_framebuffer(width: $swapchain.width, 90 | height: $swapchain.height, 91 | render_pass: render_pass, 92 | attachments: [swapchain_image_view]) 93 | end 94 | 95 | # Create graphic pipeline 96 | pipeline = device.create_pipeline(viewport: { width: $swapchain.width, height: $swapchain.height }) 97 | pipeline.add_binding_description binding: 0, 98 | stride: Vertex.size, 99 | input_rate: Vulkan::VK_VERTEX_INPUT_RATE_VERTEX 100 | 101 | pipeline.add_attribute_description binding: 0, 102 | location: 0, 103 | format: Vulkan::VK_FORMAT_R32G32_SFLOAT, 104 | offset: Vertex.offsetof('pos') 105 | 106 | pipeline.add_attribute_description binding: 0, 107 | location: 1, 108 | format: Vulkan::VK_FORMAT_R32G32B32_SFLOAT, 109 | offset: Vertex.offsetof('color') 110 | shader_stages.each { |stage| pipeline.add_shader_stage(stage) } 111 | pipeline.commit(render_pass) 112 | 113 | $command_buffers = command_pool.create_command_buffers(framebuffers.size) do |cmd, index| 114 | cmd.render_pass(render_pass, framebuffer: framebuffers[index]) do 115 | cmd.bind_pipeline(:graphics, pipeline) 116 | cmd.bind_vertex_buffer(VertexBuffer) 117 | cmd.draw(VertexData.vertices.size, 1, 0, 0) 118 | end 119 | end 120 | end 121 | 122 | rebuild_swap_chain.call 123 | 124 | MAX_FRAMES_IN_FLIGHT = 2 125 | image_available_semaphores = MAX_FRAMES_IN_FLIGHT.times.map { device.create_semaphore } 126 | render_finished_semaphores = MAX_FRAMES_IN_FLIGHT.times.map { device.create_semaphore } 127 | in_flight_fences = MAX_FRAMES_IN_FLIGHT.times.map { device.create_fence signaled: true } 128 | current_frame = 0 129 | frame_counter = 0 130 | done = false 131 | 132 | # Begin main event loop & drawing 133 | until done 134 | frame_counter += 1 135 | break if ENV['MAX_FRAMES'].to_i == frame_counter 136 | while event = SDL2::Event.poll 137 | case event 138 | when SDL2::Event::Quit, SDL2::Event::KeyDown then done = true 139 | when SDL2::Event::Window::RESIZED then rebuild_swap_chain.call 140 | end 141 | end 142 | 143 | begin 144 | in_flight_fences[current_frame].wait_and_reset 145 | image_index = $swapchain.next_image_index(semaphore: image_available_semaphores[current_frame]) 146 | graphics_queue.submit([$command_buffers[image_index]], 147 | wait_semaphores: [image_available_semaphores[current_frame]], 148 | wait_stages: [Vulkan::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT], 149 | signal_semaphores: [render_finished_semaphores[current_frame]], 150 | fence: in_flight_fences[current_frame]) 151 | presentation_queue.present(swapchains: [$swapchain], 152 | image_indices: [image_index], 153 | wait_semaphores: [render_finished_semaphores[current_frame]]) 154 | current_frame = (current_frame + 1) % MAX_FRAMES_IN_FLIGHT 155 | rescue Vulkan::Error 156 | case $!.code 157 | when Vulkan::VK_ERROR_OUT_OF_DATE_KHR, Vulkan::VK_SUBOPTIMAL_KHR 158 | rebuild_swap_chain.call 159 | else raise 160 | end 161 | end 162 | end 163 | 164 | in_flight_fences.each { |fence| fence.wait_and_reset } 165 | -------------------------------------------------------------------------------- /examples/vt03_vertex_buffer_triangle_device_local.rb: -------------------------------------------------------------------------------- 1 | # https://vulkan-tutorial.com/code/19_staging_buffer.cpp 2 | 3 | require 'bundler/setup' 4 | require 'simplecov' 5 | require 'vulkan' 6 | require 'sdl2_vulkan' 7 | 8 | Vertex = Vulkan.struct(['float pos[2]', 'float color[3]']) 9 | VertexData = Vulkan.struct('vertices[3]' => Vertex).malloc 10 | VertexData.vertices[0].pos = [ 0, -0.5]; VertexData.vertices[0].color = [1, 0, 0] 11 | VertexData.vertices[1].pos = [-0.5, 0.5]; VertexData.vertices[1].color = [0, 1, 0] 12 | VertexData.vertices[2].pos = [ 0.5, 0.5]; VertexData.vertices[2].color = [0, 0, 1] 13 | 14 | # Create a window that we plan to draw to 15 | SDL2.init(SDL2::INIT_EVERYTHING) 16 | window = SDL2::Window.create "test-vulkan", 0, 0, 640, 480, SDL2::Window::Flags::VULKAN | 17 | SDL2::Window::Flags::RESIZABLE | 18 | 0x00002000 # SDL2::Window::Flags::ALLOW_HIGHDPI 19 | 20 | # Create a Vulkan instance 21 | instance = Vulkan::Instance.new extensions: window.vk_instance_extensions 22 | 23 | # Create a rendering surface 24 | surface = instance.create_window_surface(window) 25 | 26 | # Choose an adequate physical device 27 | dev = instance.physical_devices.detect do |dev| 28 | next false unless swapchain_surface_info = dev.swapchain_surface_info(surface) 29 | builder = Vulkan::SwapchainBuilder.new(swapchain_surface_info) 30 | dev.properties[:device_type] == :discrete_gpu && 31 | (builder.format rescue false) && (builder.presentation_mode rescue false) 32 | end 33 | raise 'could not find a suitable physical device' unless dev 34 | 35 | # Find a queue family that supports graphics 36 | graphics_queue_family = dev.queue_families.detect do |family| 37 | family[:queue_count] > 0 && family.supports?(:graphics) 38 | end 39 | raise 'no graphics queue family available' unless graphics_queue_family 40 | 41 | # Find a queue family that supports presentation to the rendering surface 42 | presentation_queue_family = dev.queue_families.detect do |family| 43 | family[:queue_count] > 0 && family.supports_presentation?(surface) 44 | end 45 | raise 'no graphics queue family available' unless presentation_queue_family 46 | 47 | # Create a logical device along with the needed extensions and chosen queues 48 | if graphics_queue_family == presentation_queue_family 49 | device = dev.create queues: [{ family: graphics_queue_family, priorities: [1.0] }], 50 | extensions: ['VK_KHR_swapchain'] 51 | presentation_queue = graphics_queue = device.queue_families[0][:queues][0] 52 | else 53 | device = dev.create queues: [{ family: graphics_queue_family, priorities: [1.0] }, 54 | { family: presentation_queue_family, priorities: [1.0] }], 55 | extensions: ['VK_KHR_swapchain'] 56 | graphics_queue = device.queue_families[0][:queues][0] 57 | presentation_queue = device.queue_families[1][:queues][0] 58 | end 59 | 60 | command_pool = device.create_command_pool queue_family: graphics_queue_family 61 | 62 | StagingBuffer = device.create_buffer size: VertexData.class.size, 63 | usage: Vulkan::VK_BUFFER_USAGE_TRANSFER_SRC_BIT 64 | StagingBuffer.map do |data| 65 | data[0, VertexData.class.size] = VertexData[0, VertexData.class.size] 66 | end 67 | 68 | VertexBuffer = device.create_buffer size: VertexData.class.size, 69 | usage: Vulkan::VK_BUFFER_USAGE_TRANSFER_DST_BIT | Vulkan::VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 70 | properties: :device_local 71 | transfer_buffer = command_pool.create_command_buffer(usage: :one_time_submit) do |cmd| 72 | cmd.copy_buffer StagingBuffer, VertexBuffer 73 | end 74 | graphics_queue.submit([transfer_buffer]) 75 | graphics_queue.wait_until_idle 76 | 77 | shader_stages = [ 78 | device.create_shader_stage(file_path: File.expand_path('./shaders/vertex_buffer.vert.spv', __dir__), 79 | stage: Vulkan::VK_SHADER_STAGE_VERTEX_BIT), 80 | device.create_shader_stage(file_path: File.expand_path('./shaders/triangle.frag.spv', __dir__), 81 | stage: Vulkan::VK_SHADER_STAGE_FRAGMENT_BIT) 82 | ] 83 | 84 | rebuild_swap_chain = proc do 85 | # Create a swapchain for image presentation 86 | $swapchain = device.create_swapchain surface: surface, 87 | surface_width: window.vk_drawable_size[0], 88 | surface_height: window.vk_drawable_size[1] 89 | 90 | # Create render pass with one subpass 91 | render_pass = device.create_renderpass 92 | subpass = render_pass.add_subpass 93 | subpass.add_color_attachment_ref index: render_pass.add_attachment(format: $swapchain.format), 94 | layout: :color 95 | render_pass.commit 96 | 97 | # Create a framebuffer for each image in the swap chain 98 | framebuffers = $swapchain.image_views.map do |swapchain_image_view| 99 | device.create_framebuffer(width: $swapchain.width, 100 | height: $swapchain.height, 101 | render_pass: render_pass, 102 | attachments: [swapchain_image_view]) 103 | end 104 | 105 | # Create graphic pipeline 106 | pipeline = device.create_pipeline(viewport: { width: $swapchain.width, height: $swapchain.height }) 107 | pipeline.add_binding_description binding: 0, 108 | stride: Vertex.size, 109 | input_rate: Vulkan::VK_VERTEX_INPUT_RATE_VERTEX 110 | 111 | pipeline.add_attribute_description binding: 0, 112 | location: 0, 113 | format: Vulkan::VK_FORMAT_R32G32_SFLOAT, 114 | offset: Vertex.offsetof('pos') 115 | 116 | pipeline.add_attribute_description binding: 0, 117 | location: 1, 118 | format: Vulkan::VK_FORMAT_R32G32B32_SFLOAT, 119 | offset: Vertex.offsetof('color') 120 | shader_stages.each { |stage| pipeline.add_shader_stage(stage) } 121 | pipeline.commit(render_pass) 122 | 123 | $command_buffers = command_pool.create_command_buffers(framebuffers.size) do |cmd, index| 124 | cmd.render_pass(render_pass, framebuffer: framebuffers[index]) do 125 | cmd.bind_pipeline(:graphics, pipeline) 126 | cmd.bind_vertex_buffer(VertexBuffer) 127 | cmd.draw(VertexData.vertices.size, 1, 0, 0) 128 | end 129 | end 130 | end 131 | 132 | rebuild_swap_chain.call 133 | 134 | MAX_FRAMES_IN_FLIGHT = 2 135 | image_available_semaphores = MAX_FRAMES_IN_FLIGHT.times.map { device.create_semaphore } 136 | render_finished_semaphores = MAX_FRAMES_IN_FLIGHT.times.map { device.create_semaphore } 137 | in_flight_fences = MAX_FRAMES_IN_FLIGHT.times.map { device.create_fence signaled: true } 138 | current_frame = 0 139 | frame_counter = 0 140 | done = false 141 | 142 | # Begin main event loop & drawing 143 | until done 144 | frame_counter += 1 145 | break if ENV['MAX_FRAMES'].to_i == frame_counter 146 | while event = SDL2::Event.poll 147 | case event 148 | when SDL2::Event::Quit, SDL2::Event::KeyDown then done = true 149 | when SDL2::Event::Window::RESIZED then rebuild_swap_chain.call 150 | end 151 | end 152 | 153 | begin 154 | in_flight_fences[current_frame].wait_and_reset 155 | image_index = $swapchain.next_image_index(semaphore: image_available_semaphores[current_frame]) 156 | graphics_queue.submit([$command_buffers[image_index]], 157 | wait_semaphores: [image_available_semaphores[current_frame]], 158 | wait_stages: [Vulkan::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT], 159 | signal_semaphores: [render_finished_semaphores[current_frame]], 160 | fence: in_flight_fences[current_frame]) 161 | presentation_queue.present(swapchains: [$swapchain], 162 | image_indices: [image_index], 163 | wait_semaphores: [render_finished_semaphores[current_frame]]) 164 | current_frame = (current_frame + 1) % MAX_FRAMES_IN_FLIGHT 165 | rescue Vulkan::Error 166 | case $!.code 167 | when Vulkan::VK_ERROR_OUT_OF_DATE_KHR, Vulkan::VK_SUBOPTIMAL_KHR 168 | rebuild_swap_chain.call 169 | else raise 170 | end 171 | end 172 | end 173 | 174 | in_flight_fences.each { |fence| fence.wait_and_reset } 175 | -------------------------------------------------------------------------------- /examples/vt04_index_buffer_device_local.rb: -------------------------------------------------------------------------------- 1 | # https://vulkan-tutorial.com/code/20_index_buffer.cpp 2 | 3 | require 'bundler/setup' 4 | require 'simplecov' 5 | require 'vulkan' 6 | require 'sdl2_vulkan' 7 | 8 | Vertex = Vulkan.struct(['float pos[2]', 'float color[3]']) 9 | VertexData = Vulkan.struct('vertices[4]' => Vertex).malloc 10 | VertexData.vertices[0].pos = [-0.5, -0.5]; VertexData.vertices[0].color = [1, 0, 0] 11 | VertexData.vertices[1].pos = [ 0.5, -0.5]; VertexData.vertices[1].color = [0, 1, 0] 12 | VertexData.vertices[2].pos = [ 0.5, 0.5]; VertexData.vertices[2].color = [0, 0, 1] 13 | VertexData.vertices[3].pos = [-0.5, 0.5]; VertexData.vertices[3].color = [1, 1, 1] 14 | 15 | IndexData = Vulkan.struct(['uint16_t indices[6]']).malloc 16 | IndexData.indices = [1, 0, 2, 2, 0, 3] 17 | 18 | # Create a window that we plan to draw to 19 | SDL2.init(SDL2::INIT_EVERYTHING) 20 | window = SDL2::Window.create "test-vulkan", 0, 0, 640, 480, SDL2::Window::Flags::VULKAN | 21 | SDL2::Window::Flags::RESIZABLE | 22 | 0x00002000 # SDL2::Window::Flags::ALLOW_HIGHDPI 23 | 24 | # Create a Vulkan instance 25 | instance = Vulkan::Instance.new extensions: window.vk_instance_extensions 26 | 27 | # Create a rendering surface 28 | surface = instance.create_window_surface(window) 29 | 30 | # Choose an adequate physical device 31 | dev = instance.physical_devices.detect do |dev| 32 | next false unless swapchain_surface_info = dev.swapchain_surface_info(surface) 33 | builder = Vulkan::SwapchainBuilder.new(swapchain_surface_info) 34 | dev.properties[:device_type] == :discrete_gpu && 35 | (builder.format rescue false) && (builder.presentation_mode rescue false) 36 | end 37 | raise 'could not find a suitable physical device' unless dev 38 | 39 | # Find a queue family that supports graphics 40 | graphics_queue_family = dev.queue_families.detect do |family| 41 | family[:queue_count] > 0 && family.supports?(:graphics) 42 | end 43 | raise 'no graphics queue family available' unless graphics_queue_family 44 | 45 | # Find a queue family that supports presentation to the rendering surface 46 | presentation_queue_family = dev.queue_families.detect do |family| 47 | family[:queue_count] > 0 && family.supports_presentation?(surface) 48 | end 49 | raise 'no graphics queue family available' unless presentation_queue_family 50 | 51 | # Create a logical device along with the needed extensions and chosen queues 52 | if graphics_queue_family == presentation_queue_family 53 | device = dev.create queues: [{ family: graphics_queue_family, priorities: [1.0] }], 54 | extensions: ['VK_KHR_swapchain'] 55 | presentation_queue = graphics_queue = device.queue_families[0][:queues][0] 56 | else 57 | device = dev.create queues: [{ family: graphics_queue_family, priorities: [1.0] }, 58 | { family: presentation_queue_family, priorities: [1.0] }], 59 | extensions: ['VK_KHR_swapchain'] 60 | graphics_queue = device.queue_families[0][:queues][0] 61 | presentation_queue = device.queue_families[1][:queues][0] 62 | end 63 | 64 | command_pool = device.create_command_pool queue_family: graphics_queue_family 65 | 66 | # Populate the vertex buffer 67 | staging_buffer = device.create_buffer size: VertexData.class.size, usage: Vulkan::VK_BUFFER_USAGE_TRANSFER_SRC_BIT 68 | staging_buffer.map { |data| data[0, VertexData.class.size] = VertexData[0, VertexData.class.size] } 69 | VertexBuffer = device.create_buffer size: VertexData.class.size, 70 | usage: Vulkan::VK_BUFFER_USAGE_TRANSFER_DST_BIT | Vulkan::VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 71 | properties: :device_local 72 | vertex_transfer_buffer = command_pool.create_command_buffer(usage: :one_time_submit) { |cmd| cmd.copy_buffer staging_buffer, VertexBuffer } 73 | 74 | # Populate the index buffer 75 | staging_buffer = device.create_buffer size: IndexData.class.size, usage: Vulkan::VK_BUFFER_USAGE_TRANSFER_SRC_BIT 76 | staging_buffer.map { |data| data[0, IndexData.class.size] = IndexData[0, IndexData.class.size] } 77 | IndexBuffer = device.create_buffer size: IndexData.class.size, 78 | usage: Vulkan::VK_BUFFER_USAGE_TRANSFER_DST_BIT | Vulkan::VK_BUFFER_USAGE_INDEX_BUFFER_BIT, 79 | properties: :device_local 80 | index_transfer_buffer = command_pool.create_command_buffer(usage: :one_time_submit) { |cmd| cmd.copy_buffer staging_buffer, IndexBuffer } 81 | 82 | # Submit both transfers and wait for them to complete 83 | graphics_queue.submit([vertex_transfer_buffer, index_transfer_buffer]) 84 | graphics_queue.wait_until_idle 85 | 86 | 87 | shader_stages = [ 88 | device.create_shader_stage(file_path: File.expand_path('./shaders/vertex_buffer.vert.spv', __dir__), 89 | stage: Vulkan::VK_SHADER_STAGE_VERTEX_BIT), 90 | device.create_shader_stage(file_path: File.expand_path('./shaders/triangle.frag.spv', __dir__), 91 | stage: Vulkan::VK_SHADER_STAGE_FRAGMENT_BIT) 92 | ] 93 | 94 | rebuild_swap_chain = proc do 95 | # Create a swapchain for image presentation 96 | $swapchain = device.create_swapchain surface: surface, 97 | surface_width: window.vk_drawable_size[0], 98 | surface_height: window.vk_drawable_size[1] 99 | 100 | # Create render pass with one subpass 101 | render_pass = device.create_renderpass 102 | subpass = render_pass.add_subpass 103 | subpass.add_color_attachment_ref index: render_pass.add_attachment(format: $swapchain.format), 104 | layout: :color 105 | render_pass.commit 106 | 107 | # Create a framebuffer for each image in the swap chain 108 | framebuffers = $swapchain.image_views.map do |swapchain_image_view| 109 | device.create_framebuffer(width: $swapchain.width, 110 | height: $swapchain.height, 111 | render_pass: render_pass, 112 | attachments: [swapchain_image_view]) 113 | end 114 | 115 | # Create graphic pipeline 116 | pipeline = device.create_pipeline(viewport: { width: $swapchain.width, height: $swapchain.height }) 117 | pipeline.add_binding_description binding: 0, 118 | stride: Vertex.size, 119 | input_rate: Vulkan::VK_VERTEX_INPUT_RATE_VERTEX 120 | 121 | pipeline.add_attribute_description binding: 0, 122 | location: 0, 123 | format: Vulkan::VK_FORMAT_R32G32_SFLOAT, 124 | offset: Vertex.offsetof('pos') 125 | 126 | pipeline.add_attribute_description binding: 0, 127 | location: 1, 128 | format: Vulkan::VK_FORMAT_R32G32B32_SFLOAT, 129 | offset: Vertex.offsetof('color') 130 | shader_stages.each { |stage| pipeline.add_shader_stage(stage) } 131 | pipeline.commit(render_pass) 132 | 133 | $command_buffers = command_pool.create_command_buffers(framebuffers.size) do |cmd, index| 134 | cmd.render_pass(render_pass, framebuffer: framebuffers[index]) do 135 | cmd.bind_pipeline(:graphics, pipeline) 136 | cmd.bind_vertex_buffer(VertexBuffer) 137 | cmd.bind_index_buffer(IndexBuffer, type: :uint16) 138 | cmd.draw_indexed(IndexData.indices.size, 1, 0, 0, 0) 139 | end 140 | end 141 | end 142 | 143 | rebuild_swap_chain.call 144 | 145 | MAX_FRAMES_IN_FLIGHT = 2 146 | image_available_semaphores = MAX_FRAMES_IN_FLIGHT.times.map { device.create_semaphore } 147 | render_finished_semaphores = MAX_FRAMES_IN_FLIGHT.times.map { device.create_semaphore } 148 | in_flight_fences = MAX_FRAMES_IN_FLIGHT.times.map { device.create_fence signaled: true } 149 | current_frame = 0 150 | frame_counter = 0 151 | last_time = Time.now 152 | done = false 153 | 154 | # Begin main event loop & drawing 155 | until done 156 | frame_counter += 1 157 | break if ENV['MAX_FRAMES'].to_i == frame_counter 158 | # dump some FPS infos 159 | if frame_counter % 3000 == 0 160 | next_time = Time.now 161 | seconds = next_time - last_time 162 | fps = frame_counter / seconds 163 | p ['fps: ', fps, 'frame-time (ms): ', 1000.0 / fps] 164 | end 165 | while event = SDL2::Event.poll 166 | case event 167 | when SDL2::Event::Quit, SDL2::Event::KeyDown then done = true 168 | when SDL2::Event::Window::RESIZED then rebuild_swap_chain.call 169 | end 170 | end 171 | 172 | begin 173 | in_flight_fences[current_frame].wait_and_reset 174 | image_index = $swapchain.next_image_index(semaphore: image_available_semaphores[current_frame]) 175 | graphics_queue.submit([$command_buffers[image_index]], 176 | wait_semaphores: [image_available_semaphores[current_frame]], 177 | wait_stages: [Vulkan::VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT], 178 | signal_semaphores: [render_finished_semaphores[current_frame]], 179 | fence: in_flight_fences[current_frame]) 180 | presentation_queue.present(swapchains: [$swapchain], 181 | image_indices: [image_index], 182 | wait_semaphores: [render_finished_semaphores[current_frame]]) 183 | current_frame = (current_frame + 1) % MAX_FRAMES_IN_FLIGHT 184 | rescue Vulkan::Error 185 | case $!.code 186 | when Vulkan::VK_ERROR_OUT_OF_DATE_KHR, Vulkan::VK_SUBOPTIMAL_KHR 187 | rebuild_swap_chain.call 188 | else raise 189 | end 190 | end 191 | end 192 | 193 | in_flight_fences.each { |fence| fence.wait_and_reset } 194 | -------------------------------------------------------------------------------- /lib/fiddle_ext.rb: -------------------------------------------------------------------------------- 1 | require 'fiddle' 2 | require 'fiddle/import' 3 | 4 | module Fiddle 5 | class Pointer 6 | # Copies data from the specified pointer into this one, starting at offset 7 | # 0 and ending at either this pointer's size or the size of `other`, 8 | # whichever is smaller. 9 | def copy_from(other) 10 | size = self.size > other.size ? other.size : self.size 11 | self[0, size] = other 12 | self 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/vulkan-ruby.rb: -------------------------------------------------------------------------------- 1 | require 'vulkan' 2 | -------------------------------------------------------------------------------- /lib/vulkan.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | require 'rubygems/version' 3 | require 'vulkan/platform' 4 | require 'fiddle_ext' 5 | 6 | module Vulkan 7 | extend Fiddle::Importer 8 | extend Vulkan::Platform 9 | 10 | class << self 11 | def root 12 | Pathname.new(__dir__).join('vulkan') 13 | end 14 | 15 | def parse_signature(sig, type_alias = @type_alias) 16 | super(sig, type_alias) 17 | end 18 | 19 | def vulkan_library_candidates 20 | [ 21 | ENV['PATH_TO_VULKAN'], 22 | *case os 23 | when :windows then ['vulkan-1.dll'] 24 | when :osx then ['libvulkan.dylib', 'libvulkan.1.dylib', 'libMoltenVK.dylib'] 25 | when :linux then ['libvulkan.so', 'libvulkan.so.1'] 26 | else [] 27 | end 28 | ].compact 29 | end 30 | 31 | def load_vulkan_library 32 | candidates = vulkan_library_candidates 33 | begin 34 | dlload candidates.shift 35 | rescue 36 | retry if candidates.any? 37 | raise "could not determine vulkan library to load for this OS (#{os.inspect}): try passing PATH_TO_VULKAN" 38 | end 39 | end 40 | 41 | def format_has_stencil_component?(format) 42 | case format 43 | when VK_FORMAT_D32_SFLOAT_S8_UINT, :d32_sfloat_s8_uint then true 44 | when VK_FORMAT_D24_UNORM_S8_UINT, :d24_unorm_s8_uint then true 45 | else false 46 | end 47 | end 48 | 49 | def num_mip_levels(*sizes) 50 | Math.log2(sizes.max).floor + 1 51 | end 52 | end 53 | 54 | require 'vulkan/generated' 55 | require 'vulkan/error' 56 | end 57 | 58 | require 'vulkan/version' 59 | require 'vulkan/checks' 60 | require 'vulkan/conversions' 61 | require 'vulkan/finalizer' 62 | require 'vulkan/framebuffer' 63 | require 'vulkan/surface' 64 | require 'vulkan/pipeline' 65 | require 'vulkan/window_surface' 66 | require 'vulkan/dispatch_table' 67 | require 'vulkan/physical_device' 68 | require 'vulkan/logical_device' 69 | require 'vulkan/instance' 70 | require 'vulkan/queue_family' 71 | require 'vulkan/swapchain' 72 | require 'vulkan/swapchain_surface_info' 73 | require 'vulkan/swapchain_builder' 74 | require 'vulkan/image_view' 75 | require 'vulkan/shader_stage' 76 | require 'vulkan/render_pass' 77 | require 'vulkan/render_pass/subpass' 78 | require 'vulkan/command_pool' 79 | require 'vulkan/command_buffer' 80 | require 'vulkan/semaphore' 81 | require 'vulkan/fence' 82 | require 'vulkan/queue' 83 | require 'vulkan/buffer' 84 | require 'vulkan/descriptor_pool' 85 | require 'vulkan/descriptor_set' 86 | require 'vulkan/descriptor_set_layout' 87 | require 'vulkan/image' 88 | require 'vulkan/memory' 89 | require 'vulkan/buffer_memory' 90 | require 'vulkan/image_memory' 91 | require 'vulkan/sampler' 92 | require 'vulkan/memory_barrier' 93 | require 'vulkan/buffer_memory_barrier' 94 | -------------------------------------------------------------------------------- /lib/vulkan/buffer.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class Buffer 3 | include Vulkan::Checks 4 | include Vulkan::Conversions 5 | include Vulkan::Finalizer 6 | 7 | attr_reader :memory 8 | attr_reader :size 9 | attr_reader :usage 10 | 11 | def initialize(vk, physical_device, size:, 12 | usage:, 13 | sharing_mode: VK_SHARING_MODE_EXCLUSIVE, 14 | flags: 0, 15 | **memory_args) 16 | @vk = vk 17 | @size = size 18 | @mapped = Vulkan.create_value('void *', nil) 19 | 20 | buffer_info = VkBufferCreateInfo.malloc 21 | buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO 22 | buffer_info.size = size 23 | buffer_info.usage = syms_to_buffer_usage_flags(usage) 24 | buffer_info.sharingMode = sharing_mode 25 | buffer_info.flags = flags 26 | handle_p = Vulkan.create_value('void *', nil) 27 | check_result @vk.vkCreateBuffer(vk.device, buffer_info, nil, handle_p) 28 | @handle = handle_p.value 29 | @memory = Vulkan::BufferMemory.new(@vk, physical_device, owner: self, 30 | **memory_args) 31 | finalize_with @vk, :vkDestroyBuffer, vk.device, @handle, nil 32 | end 33 | 34 | def create_barrier(**args) 35 | @memory.create_barrier(**args) 36 | end 37 | 38 | def map(*args, &block) 39 | memory.map(*args, &block) 40 | end 41 | 42 | def flush(...) 43 | memory.flush(...) 44 | end 45 | 46 | def flush_all(...) 47 | memory.flush_all(...) 48 | end 49 | 50 | def invalidate(...) 51 | memory.invalidate(...) 52 | end 53 | 54 | def invalidate_all(...) 55 | memory.invalidate_all(...) 56 | end 57 | 58 | def mapped? 59 | memory.mapped? 60 | end 61 | 62 | def unmap 63 | memory.unmap 64 | end 65 | 66 | def data 67 | memory.data 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/vulkan/buffer_memory.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class BufferMemory < Memory 3 | def initialize(vk, physical_device, owner:, **options) 4 | @owner = owner 5 | super(vk, physical_device, **options) 6 | end 7 | 8 | def query_memory_requirements 9 | VkMemoryRequirements.malloc.tap do |req| 10 | @vk.vkGetBufferMemoryRequirements(@vk.device, @owner.to_ptr, req) 11 | end 12 | end 13 | 14 | def bind 15 | @vk.vkBindBufferMemory(@vk.device, @owner.to_ptr, to_ptr, 0); 16 | end 17 | 18 | def create_barrier(**args) 19 | Vulkan::BufferMemoryBarrier.new(buffer: @owner, **args) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/vulkan/buffer_memory_barrier.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class BufferMemoryBarrier 3 | include Vulkan::Conversions 4 | 5 | def initialize(src_access:, 6 | dst_access:, 7 | src_queue_family_index: VK_QUEUE_FAMILY_IGNORED, 8 | dst_queue_family_index: VK_QUEUE_FAMILY_IGNORED, 9 | buffer:, 10 | offset: 0, 11 | size: VK_WHOLE_SIZE) 12 | @struct = VkBufferMemoryBarrier.malloc 13 | @struct.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER 14 | self.src_access = src_access 15 | self.dst_access = dst_access 16 | self.src_queue_family_index = src_queue_family_index 17 | self.dst_queue_family_index = dst_queue_family_index 18 | self.buffer = buffer 19 | self.offset = offset 20 | self.size = size 21 | end 22 | 23 | def src_access; @src_access; end 24 | def src_access=(s); @src_access = s; @struct.srcAccessMask = syms_to_access_mask(s); end 25 | def dst_access; @dst_access; end 26 | def dst_access=(s); @dst_access = s; @struct.dstAccessMask = syms_to_access_mask(s); end 27 | def src_queue_family_index; @struct.srcQueueFamilyIndex; end 28 | def src_queue_family_index=(s); @struct.srcQueueFamilyIndex = s; end 29 | def dst_queue_family_index; @struct.dstQueueFamilyIndex; end 30 | def dst_queue_family_index=(s); @struct.dstQueueFamilyIndex = s; end 31 | def buffer; @buffer; end 32 | def buffer=(b); @buffer = b; @struct.buffer = b.to_ptr; end 33 | def offset; @struct.offset; end 34 | def offset=(o); @struct.offset = o; end 35 | def size; @struct.size; end 36 | def size=(s); @struct.size = s; end 37 | 38 | def to_ptr 39 | @struct.to_ptr 40 | end 41 | end 42 | end 43 | 44 | -------------------------------------------------------------------------------- /lib/vulkan/checks.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | module Checks 3 | # Checks whether the result was ok, raising the appropriate error if not. 4 | def check_result(result) 5 | case result 6 | when VK_SUCCESS then return result 7 | else raise Vulkan::Error::ResultCheckError, result 8 | end 9 | end 10 | 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/vulkan/command_pool.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class CommandPool 3 | include Vulkan::Checks 4 | include Vulkan::Conversions 5 | include Vulkan::Finalizer 6 | 7 | def initialize(vk, queue_family:, transient: false, allow_reset: false) 8 | @vk = vk 9 | pool_info = VkCommandPoolCreateInfo.malloc 10 | pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO 11 | pool_info.queueFamilyIndex = queue_family_to_index(queue_family) 12 | pool_info.flags = (transient ? VK_COMMAND_POOL_CREATE_TRANSIENT_BIT : 0) | 13 | (allow_reset ? VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT : 0) 14 | command_pool_p = Vulkan.create_value('void *', nil) 15 | check_result @vk.vkCreateCommandPool(vk.device, pool_info, nil, command_pool_p) 16 | @handle = command_pool_p.value 17 | finalize_with vk, :vkDestroyCommandPool, vk.device, @handle, nil 18 | end 19 | 20 | def create_command_buffer(**args, &block) 21 | create_command_buffers(1, **args, &block)[0] 22 | end 23 | 24 | def create_command_buffers(count, **args, &block) 25 | Vulkan::CommandBuffer.create(@vk, self, count: count, **args, &block) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/vulkan/descriptor_pool.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class DescriptorPool 3 | include Vulkan::Checks 4 | include Vulkan::Conversions 5 | include Vulkan::Finalizer 6 | 7 | def initialize(vk, flags: VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, max_sets:, pool_sizes:) 8 | @vk = vk 9 | @flags = flags 10 | pool_info = VkDescriptorPoolCreateInfo.malloc 11 | pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO 12 | pool_info.poolSizeCount = pool_sizes.size 13 | pool_info.pPoolSizes = array_of_structures(pool_sizes.map { |info| build_pool_size(**info) }) 14 | pool_info.flags = @flags 15 | pool_info.maxSets = max_sets 16 | handle_p = Vulkan.create_value('VkDescriptorPool') 17 | check_result @vk.vkCreateDescriptorPool(@vk.device, pool_info, nil, handle_p) 18 | @handle = handle_p.value 19 | finalize_with @vk, :vkDestroyDescriptorPool, @vk.device, @handle, nil 20 | end 21 | 22 | def descriptor_sets_freeable? 23 | @flags & VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT == VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT 24 | end 25 | 26 | def alloc(layouts:) 27 | count = layouts.size 28 | alloc_info = VkDescriptorSetAllocateInfo.malloc 29 | alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO 30 | alloc_info.descriptorPool = to_ptr 31 | alloc_info.descriptorSetCount = count 32 | alloc_info.pSetLayouts = array_of_structures(layouts.map { |layout| Vulkan.create_value('void *', layout.to_ptr.to_i) }) 33 | sets_p = Vulkan.struct(['VkDescriptorSet sets[%d]' % count]).malloc 34 | check_result @vk.vkAllocateDescriptorSets(@vk.device, alloc_info, sets_p) 35 | count.times.map { |i| Vulkan::DescriptorSet.new(@vk, self, layouts[i], sets_p.sets[i], descriptor_sets_freeable?) } 36 | end 37 | 38 | def build_pool_size(type:, count:) 39 | VkDescriptorPoolSize.malloc.tap do |pool_size| 40 | pool_size.type = sym_to_descriptor_type(type) 41 | pool_size.descriptorCount = count 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/vulkan/descriptor_set.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class DescriptorSet 3 | include Vulkan::Conversions 4 | include Vulkan::Finalizer 5 | 6 | class DescriptorWrite 7 | include Vulkan::Conversions 8 | 9 | def initialize(owner, layout) 10 | @changed = false 11 | @writer = VkWriteDescriptorSet.malloc 12 | @writer.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET 13 | @writer.dstSet = owner.to_ptr 14 | @writer.dstBinding = layout.binding 15 | @writer.dstArrayElement = 0 16 | @writer.descriptorType = layout.descriptorType 17 | @writer.descriptorCount = layout.descriptorCount 18 | @writer.pBufferInfo = nil 19 | @writer.pImageInfo = nil 20 | @writer.pTexelBufferView = nil 21 | end 22 | 23 | def set_buffer(buffer, offset: 0, range: buffer.size, array_element: 0) 24 | @changed = true 25 | @info = VkDescriptorBufferInfo.malloc 26 | @info.buffer = buffer.to_ptr 27 | @info.offset = offset 28 | @info.range = range 29 | @writer.pImageInfo = @writer.pTexelBufferView = nil 30 | @writer.pBufferInfo = @info 31 | @writer.dstArrayElement = array_element 32 | end 33 | 34 | def set_image_view_and_sampler(image_view, sampler, layout: :shader_read_only_optimal, array_element: 0) 35 | @changed = true 36 | @info = VkDescriptorImageInfo.malloc 37 | @info.imageLayout = sym_to_image_layout(layout) 38 | @info.imageView = image_view.to_ptr 39 | @info.sampler = sampler.to_ptr 40 | @writer.pBufferInfo = @writer.pTexelBufferView = nil 41 | @writer.pImageInfo = @info 42 | @writer.dstArrayElement = array_element 43 | end 44 | 45 | def to_ptr 46 | @writer.to_ptr 47 | end 48 | 49 | def changed? 50 | @changed 51 | end 52 | end 53 | 54 | def initialize(vk, pool, layout, handle, freeable) 55 | @vk = vk 56 | @handle = handle 57 | if freeable 58 | handle_p = Vulkan.create_value('void *', handle) 59 | finalize_with vk, :vkFreeDescriptorSets, vk.device, pool, 1, handle_p 60 | end 61 | 62 | @descriptor_writes = layout.descriptors.each_with_index.map do |desc_layout, i| 63 | DescriptorWrite.new(self, desc_layout) 64 | end 65 | end 66 | 67 | def [](index) 68 | @descriptor_writes[index] 69 | end 70 | 71 | def commit 72 | writes = @descriptor_writes.select { |write| write.changed? } 73 | return unless writes.any? 74 | writes_p = array_of_structures(writes) 75 | @vk.vkUpdateDescriptorSets(@vk.device, writes.size, writes_p, 0, nil) 76 | end 77 | 78 | def update_texel_view(*, **) 79 | raise NotImplemented 80 | end 81 | 82 | def update_image(image) 83 | raise NotImplemented 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/vulkan/descriptor_set_layout.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class DescriptorSetLayout 3 | include Vulkan::Checks 4 | include Vulkan::Conversions 5 | include Vulkan::Finalizer 6 | 7 | attr_reader :descriptors 8 | attr_reader :types 9 | 10 | def initialize(vk, *types, descriptors:) 11 | @vk = vk 12 | @types = types 13 | @descriptors = descriptors.map { |d| build_descriptor(**d) } 14 | value_p = Vulkan.create_value('VkDescriptorSetLayout', nil) 15 | check_result @vk.vkCreateDescriptorSetLayout(@vk.device, build_layout_info, nil, value_p) 16 | @handle = value_p.value 17 | finalize_with @vk, :vkDestroyDescriptorSetLayout, @vk.device, value_p.value, nil 18 | end 19 | 20 | def build_layout_info 21 | VkDescriptorSetLayoutCreateInfo.malloc.tap do |layout| 22 | layout.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO 23 | layout.flags = syms_to_descriptor_set_layout_type_flags(@types) 24 | layout.bindingCount = descriptors.size 25 | layout.pBindings = array_of_structures(@descriptors) 26 | end 27 | end 28 | 29 | def build_descriptor(binding:, type:, count: 1, stages:, samplers: []) 30 | VkDescriptorSetLayoutBinding.malloc.tap do |descr| 31 | descr.binding = binding 32 | descr.descriptorType = sym_to_descriptor_type(type) 33 | descr.descriptorCount = count 34 | descr.stageFlags = syms_to_shader_stage_flags(stages) 35 | descr.pImmutableSamplers = array_of_pointers(samplers) 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/vulkan/dispatch_table.rb: -------------------------------------------------------------------------------- 1 | require 'vulkan/platform' 2 | 3 | module Vulkan 4 | class DispatchTable 5 | include Vulkan::Platform 6 | 7 | def initialize(instance, device) 8 | # NOTE it's important we keep handles to both instance and device to 9 | # avoid garbage collection of those objects if they should go out of 10 | # scope in the greater program. As long as anyone keeps a handle to 11 | # this dispatch table, they are still using both the device and 12 | # instance, and premature GC of those objects can be catastrophic 13 | # (segfaults). Because of the #to_ptr below, this may constitute a bit 14 | # of a refactor later on, for clarity's sake -- but for now merely 15 | # keeping the handles around is good enough. 16 | @_instance = instance 17 | @_device = device 18 | raise ArgumentError, "instance must be a Vulkan::Instance or nil" unless @_instance.nil? || @_instance.kind_of?(Vulkan::Instance) 19 | raise ArgumentError, "device must be a Vulkan::LogicalDevice or nil" unless @_device.nil? || @_device.kind_of?(Vulkan::LogicalDevice) 20 | 21 | @instance = instance&.to_ptr 22 | @device = device&.to_ptr 23 | @instance_functions = [] 24 | @device_functions = [] 25 | end 26 | 27 | def instance 28 | @_instance 29 | end 30 | 31 | def device 32 | @_device 33 | end 34 | 35 | def respond_to?(method_name) 36 | super || Vulkan.function_registry.key?(method_name) 37 | end 38 | 39 | def inspect 40 | "#<#{self.class} instance@#{@instance.to_i.to_s(16)} device@#{@device.to_i.to_s(16)}>" 41 | end 42 | 43 | def call_trace(name, calling_convention, *params) 44 | if ENV['CALL_TRACE'] 45 | args_s = params.map do |arg| 46 | arg = arg.to_ptr if arg.respond_to?(:to_ptr) 47 | arg.kind_of?(Fiddle::Pointer) ? arg.to_i.to_s(16) : arg.inspect 48 | end 49 | suffix = " as #{calling_convention}" if calling_convention != :default 50 | puts "CALL: #{@instance.to_i.to_s(16)}/#{@device.to_i.to_s(16)}/#{name}(#{args_s.join(', ')})#{suffix}" 51 | end 52 | end 53 | 54 | def vkGetInstanceProcAddr(*args) 55 | call_trace(:vkGetInstanceProcAddr, calling_convention_human, *args) 56 | Vulkan.vkGetInstanceProcAddr(*args) 57 | end 58 | 59 | def method_missing(name, *args) 60 | super unless respond_to?(name) 61 | 62 | # Calling vkDestroyInstance will implicitly free everything associated 63 | # with it; at shutdown, since order of finalization is not guaranteed, 64 | # the instance may be already destroyed and this can lead to 65 | # double-freeing and crashing (at least this is true on Mac as of 66 | # vulkan-sdk 1.3.296.0). So to avoid crashing on shutdown, we'll 67 | # try to discard any 'vkDestroy' associated with an instance after 68 | # vkDestroyInstance is already called. 69 | 70 | function_info = Vulkan.function_registry[name] 71 | is_instance_function = false 72 | if @instance.nil? || @device.nil? || name == :vkGetDeviceProcAddr 73 | # instance function 74 | puts "LOOKUP: Looking up instance function #{name.inspect}" if ENV['CALL_TRACE'] 75 | return nil if @_instance&.finalized? 76 | addr = vkGetInstanceProcAddr(@instance, name.to_s) 77 | is_instance_function = true if addr.to_i != 0 78 | else 79 | # device function, but fall back to instance if device func lookup fails 80 | puts "LOOKUP: Looking up device function #{name.inspect}" if ENV['CALL_TRACE'] 81 | return nil if @_instance&.finalized? || @_device&.finalized? 82 | addr = vkGetDeviceProcAddr(@device, name.to_s) 83 | if addr.to_i == 0 84 | puts "LOOKUP: Not a device function - Looking up instance function #{name.inspect}" if ENV['CALL_TRACE'] 85 | addr = vkGetInstanceProcAddr(@instance, name.to_s) 86 | is_instance_function = true if addr.to_i != 0 87 | end 88 | end 89 | 90 | super if addr.to_i == 0 # method not found 91 | func = Fiddle::Function.new(addr, function_info[:params_types], function_info[:return_type], calling_convention, name: name) 92 | # Better performance if we're not constantly checking the name of every 93 | # method we just called. 94 | stubber = case name 95 | when :vkDestroyInstance then proc { stub_vk_instance_methods } 96 | when :vkDestroyDevice then proc { stub_vk_device_methods } 97 | else nil 98 | end 99 | define_singleton_method(name) do |*params| 100 | call_trace(name, calling_convention_human, *params) 101 | func.call(*params).tap { stubber&.call } 102 | end 103 | (is_instance_function ? @instance_functions : @device_functions) << name 104 | send(name, *args) 105 | end 106 | 107 | def stub_vk_instance_methods 108 | @_instance&.finalized = true 109 | @instance_functions.each do |name| 110 | define_singleton_method(name) do |*params| 111 | # to avoid crashing by calling a method on an already destroyed 112 | # instance, do nothing. 113 | end 114 | end 115 | stub_vk_device_methods 116 | end 117 | 118 | def stub_vk_device_methods 119 | @_device&.finalized = true 120 | @device_functions.each do |name| 121 | define_singleton_method(name) do |*params| 122 | # to avoid crashing by calling a method on an already destroyed 123 | # instance, do nothing. 124 | end 125 | end 126 | end 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /lib/vulkan/error.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class Error < StandardError 3 | class ResultCheckError < Error 4 | # TODO populate these during enum generation 5 | CODES = { 6 | VK_SUCCESS => "Command completed successfully", 7 | VK_NOT_READY => "A fence or query has not yet completed", 8 | VK_TIMEOUT => "A wait operation has not completed in the specified time", 9 | VK_EVENT_SET => "An event is signaled", 10 | VK_EVENT_RESET => "An event is unsignaled", 11 | VK_INCOMPLETE => "A return array was too small for the result", 12 | VK_ERROR_OUT_OF_HOST_MEMORY => "A host memory allocation has failed", 13 | VK_ERROR_OUT_OF_DEVICE_MEMORY => "A device memory allocation has failed", 14 | VK_ERROR_INITIALIZATION_FAILED => "Initialization of a object has failed", 15 | VK_ERROR_DEVICE_LOST => "The logical device has been lost. See <>", 16 | VK_ERROR_MEMORY_MAP_FAILED => "Mapping of a memory object has failed", 17 | VK_ERROR_LAYER_NOT_PRESENT => "Layer specified does not exist", 18 | VK_ERROR_EXTENSION_NOT_PRESENT => "Extension specified does not exist", 19 | VK_ERROR_FEATURE_NOT_PRESENT => "Requested feature is not available on this device", 20 | VK_ERROR_INCOMPATIBLE_DRIVER => "Unable to find a compatible Vulkan driver", 21 | VK_ERROR_TOO_MANY_OBJECTS => "Too many objects of the type have already been created", 22 | VK_ERROR_FORMAT_NOT_SUPPORTED => "Requested format is not supported on this device", 23 | VK_ERROR_FRAGMENTED_POOL => "A requested pool allocation has failed due to fragmentation of the pool's memory", 24 | VK_ERROR_OUT_OF_DATE_KHR => "Swapchain is out of date", 25 | VK_SUBOPTIMAL_KHR => "Swapchain is suboptimal" 26 | } 27 | 28 | attr_reader :code 29 | 30 | def initialize(code) 31 | raise 'BUG: error code is nil, probably a in this gem' if code.nil? 32 | @code = code 33 | super(CODES[code] || "Unknown error code: 0x#{code.to_s(16)} (#{code})") 34 | end 35 | end 36 | 37 | class UnsupportedFeature < StandardError 38 | def initialize(ruby_name, vulkan_name) 39 | super "You requested an unsupported feature: #{vulkan_name} (#{ruby_name.inspect})" 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/vulkan/fence.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class Fence 3 | include Vulkan::Checks 4 | include Vulkan::Finalizer 5 | 6 | def initialize(vk, signaled: false) 7 | @vk = vk 8 | fence_info = VkFenceCreateInfo.malloc 9 | fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO 10 | fence_info.flags = (signaled ? VK_FENCE_CREATE_SIGNALED_BIT : 0) 11 | fence_p = Vulkan.create_value('void *', nil) 12 | check_result @vk.vkCreateFence(@vk.device, fence_info, nil, fence_p) 13 | @handle = fence_p.value 14 | finalize_with @vk, :vkDestroyFence, @vk.device, @handle, nil 15 | @self_p = Vulkan.create_value('void *', nil) 16 | @self_p.value = @handle 17 | end 18 | 19 | def wait 20 | @vk.vkWaitForFences(@vk.device, 1, @self_p, VK_TRUE, 0xffffffffffffffff) 21 | end 22 | 23 | def reset 24 | @vk.vkResetFences(@vk.device, 1, @self_p) 25 | end 26 | 27 | def wait_and_reset 28 | wait 29 | reset 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/vulkan/finalizer.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | module Finalizer 3 | module ClassMethods 4 | def finalizer(vk, cleanup_method_name, *cleanup_method_args) 5 | proc do 6 | begin 7 | unless ENV['NO_FINALIZE'] 8 | if ENV['CALL_TRACE'] 9 | cleanup_method_args_s = cleanup_method_args.map do |arg| 10 | arg = arg.to_ptr if arg.respond_to?(:to_ptr) 11 | arg = arg.hexaddr if arg.respond_to?(:hexaddr) 12 | arg.kind_of?(Fiddle::Pointer) ? arg.to_i.to_s(16) : arg.inspect 13 | end 14 | puts "FINALIZE #{name}: #{vk.instance.to_i.to_s(16)}/#{vk.device.to_i.to_s(16)}/(#{cleanup_method_args_s.join(', ')})" 15 | end 16 | 17 | vk.send(cleanup_method_name, *cleanup_method_args) 18 | end 19 | rescue 20 | puts $! 21 | $!.backtrace.each { |line| puts " #{line}" } 22 | end 23 | end 24 | end 25 | end 26 | 27 | def self.included(base) 28 | base.module_eval { extend Vulkan::Finalizer::ClassMethods } 29 | end 30 | 31 | def finalize_with(vk, cleanup_method_name, *cleanup_method_args) 32 | ObjectSpace.define_finalizer(self, self.class.finalizer(vk, cleanup_method_name, *cleanup_method_args)) 33 | end 34 | 35 | def to_ptr 36 | @handle 37 | end 38 | 39 | def hexaddr 40 | to_ptr.to_i.to_s(16) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/vulkan/framebuffer.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class Framebuffer 3 | include Vulkan::Checks 4 | include Vulkan::Conversions 5 | include Vulkan::Finalizer 6 | 7 | attr_reader :width 8 | attr_reader :height 9 | attr_reader :layers 10 | 11 | def initialize(vk, attachments:, width:, height:, render_pass:, layers: 1) 12 | @vk = vk 13 | @width = width 14 | @height = height 15 | @layers = layers 16 | @attachments = attachments 17 | 18 | framebuffer_info = VkFramebufferCreateInfo.malloc 19 | framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO 20 | framebuffer_info.renderPass = render_pass 21 | framebuffer_info.attachmentCount = attachments.size 22 | framebuffer_info.pAttachments = array_of_pointers(attachments) 23 | framebuffer_info.width = width 24 | framebuffer_info.height = height 25 | framebuffer_info.layers = layers 26 | 27 | framebuffer_p = Vulkan.create_value("void *", nil) 28 | check_result @vk.vkCreateFramebuffer(vk.device, framebuffer_info, nil, framebuffer_p) 29 | @handle = framebuffer_p.value 30 | finalize_with vk, :vkDestroyFramebuffer, vk.device, @handle, nil 31 | end 32 | 33 | def to_ptr 34 | @handle 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/vulkan/generated.rb: -------------------------------------------------------------------------------- 1 | require 'vulkan/manual_types' 2 | 3 | module Vulkan 4 | load_vulkan_library 5 | include Vulkan::ManualTypes 6 | 7 | class << self 8 | @@lookup_map = {} 9 | @@function_registry = {} 10 | 11 | # Returns a dispatch table for the given instance and device. 12 | def [](instance, device) 13 | hash = [instance, device].hash 14 | @@lookup_map[hash] ||= Vulkan::DispatchTable.new(instance, device) 15 | end 16 | 17 | def register_function(proto, params) 18 | # DispatchTable can use method_missing to know the name of a function to 19 | # look up, but we need to know the params and its return type, which come 20 | # from vk.xml and which are foreign concepts to ruby so we need to 21 | # persist that information for lookup within method_missing. See 22 | # DispatchTable#method_missing. 23 | signature = "#{proto}(#{params})" 24 | name, ctype, argtype = parse_signature(signature, Vulkan.send(:type_alias)) 25 | function_registry[name.intern] = { 26 | name: name, 27 | return_type: ctype, 28 | params_types: argtype, 29 | prototype: proto, 30 | params: params, 31 | signature: signature 32 | } 33 | end 34 | 35 | def function_registry 36 | @@function_registry 37 | end 38 | end 39 | 40 | require_relative 'generated/types' 41 | require_relative 'generated/enums' 42 | require_relative 'generated/structs' 43 | 44 | extern "PFN_vkVoidFunction vkGetInstanceProcAddr(VkInstance instance, const char* pName)", :stdcall 45 | require_relative 'generated/commands' 46 | # require_relative 'generated/extensions' 47 | end 48 | -------------------------------------------------------------------------------- /lib/vulkan/generated/version.rb: -------------------------------------------------------------------------------- 1 | # vulkan-ruby 1.3.207.1 2 | # 3 | # => https://github.com/sinisterchipmunk/vulkan-ruby 4 | # 5 | # [NOTICE] This is an automatically generated file. 6 | 7 | module Vulkan 8 | VK_API_VERSION_VARIANT = 0 9 | VK_API_VERSION_MAJOR = 1 10 | VK_API_VERSION_MINOR = 3 11 | VK_API_VERSION_PATCH = 207 12 | VK_HEADER_VERSION_COMPLETE = [VK_API_VERSION_VARIANT, 13 | VK_API_VERSION_MAJOR, 14 | VK_API_VERSION_MINOR, 15 | VK_API_VERSION_PATCH] 16 | end 17 | -------------------------------------------------------------------------------- /lib/vulkan/image.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class Image 3 | include Vulkan::Checks 4 | include Vulkan::Conversions 5 | include Vulkan::Finalizer 6 | 7 | attr_reader :dimensions 8 | attr_reader :width 9 | attr_reader :height 10 | attr_reader :depth 11 | attr_reader :mip_levels 12 | attr_reader :array_layers 13 | attr_reader :format 14 | attr_reader :tiling 15 | attr_reader :initial_layout 16 | attr_reader :layout 17 | attr_reader :usage 18 | attr_reader :sharing 19 | attr_reader :samples 20 | attr_reader :memory 21 | 22 | # You should only set this if you have transitioned the image to a 23 | # different layout outside of this class, by working directly with 24 | # a command buffer. 25 | attr_writer :layout 26 | 27 | def initialize(vk, physical_device, 28 | dimensions: , 29 | width: , 30 | height: , 31 | depth: , 32 | mip_levels: 1, 33 | array_layers: 1, 34 | format: :r8g8b8a8_unorm, 35 | tiling: :optimal, 36 | initial_layout: :undefined, 37 | usage: [:transfer_dst, :sampled], 38 | sharing: :exclusive, 39 | samples: 1, 40 | flags: [], 41 | properties: :device_local) 42 | @vk = vk 43 | @dimensions = dimensions 44 | @width = width 45 | @height = height 46 | @depth = depth 47 | @mip_levels = mip_levels 48 | @array_layers = array_layers 49 | @format = format 50 | @tiling = tiling 51 | @initial_layout = @layout = initial_layout 52 | @usage = usage 53 | @sharing = sharing 54 | @samples = samples 55 | image_info = VkImageCreateInfo.malloc 56 | image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO 57 | image_info.imageType = sym_to_image_type((1..3).include?(dimensions) ? :"#{dimensions}d" : dimensions) 58 | image_info.extent.width = width 59 | image_info.extent.height = height 60 | image_info.extent.depth = depth 61 | image_info.mipLevels = mip_levels 62 | image_info.arrayLayers = array_layers 63 | image_info.format = sym_to_image_format(format) 64 | image_info.tiling = sym_to_image_tiling(tiling) 65 | image_info.initialLayout = sym_to_image_layout(initial_layout) 66 | image_info.usage = syms_to_image_usage_flags(usage) 67 | image_info.sharingMode = sym_to_sharing_mode(sharing) 68 | image_info.samples = sym_to_samples(samples) 69 | image_info.flags = syms_to_image_create_flags(flags) 70 | handle_p = Vulkan.create_value('VkImage') 71 | check_result @vk.vkCreateImage(@vk.device, image_info, nil, handle_p) 72 | @handle = handle_p.value 73 | @memory = Vulkan::ImageMemory.new(@vk, physical_device, owner: self, properties: properties) 74 | finalize_with @vk, :vkDestroyImage, @vk.device, @handle, nil 75 | end 76 | 77 | def create_view(format: self.format, 78 | base_mip_level: 0, 79 | level_count: mip_levels - base_mip_level, 80 | base_array_layer: 0, 81 | layer_count: array_layers - base_array_layer, 82 | aspects: :color, 83 | **other_image_view_args) 84 | ImageView.new(@vk, self, format, 85 | base_mip_level: base_mip_level, 86 | level_count: level_count, 87 | base_array_layer: base_array_layer, 88 | layer_count: layer_count, 89 | aspects: aspects, 90 | **other_image_view_args) 91 | end 92 | 93 | def detect_transition_access_and_stage_flags(from, to) 94 | from, to = sym_to_image_layout(from), sym_to_image_layout(to) 95 | if from == VK_IMAGE_LAYOUT_UNDEFINED && 96 | to == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL 97 | return { 98 | src_access: 0, 99 | dst_access: :transfer_write, 100 | src_stages: :top_of_pipe, 101 | dst_stages: :transfer, 102 | dependencies: 0 103 | } 104 | elsif from == VK_IMAGE_LAYOUT_UNDEFINED && 105 | to == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL 106 | return { 107 | src_access: 0, 108 | dst_access: :shader_read, 109 | src_stages: :color_attachment_output, 110 | dst_stages: :fragment_shader, 111 | dependencies: 0 112 | } 113 | elsif from == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && 114 | to == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL 115 | return { 116 | src_access: :transfer_write, 117 | dst_access: :shader_read, 118 | src_stages: :transfer, 119 | dst_stages: :fragment_shader, 120 | dependencies: 0 121 | } 122 | elsif from == VK_IMAGE_LAYOUT_UNDEFINED && 123 | to == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL 124 | return { 125 | src_access: 0, 126 | dst_access: [ :depth_stencil_attachment_read, :depth_stencil_attachment_write ], 127 | src_stages: :top_of_pipe, 128 | dst_stages: :early_fragment_tests, 129 | dependencies: 0 130 | } 131 | elsif from == VK_IMAGE_LAYOUT_UNDEFINED && 132 | to == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL 133 | return { 134 | src_access: 0, 135 | dst_access: [ :color_attachment_read, :color_attachment_write ], 136 | src_stages: :top_of_pipe, 137 | dst_stages: :color_attachment_output, 138 | dependencies: 0 139 | } 140 | else 141 | raise ArgumentError, "unsupported layout transition (%s to %s)!" % [from.inspect, to.inspect] 142 | end 143 | end 144 | 145 | # Transitions the layout for this image using the given command buffer. 146 | # Does not submit the command buffer. Use #transition_layout if you want 147 | # to be sure the image has fully transitioned before continuing. 148 | def transition_layout_using(cmd, 149 | from: self.layout, 150 | to:, 151 | src_queue_family: nil, 152 | dst_queue_family: nil, 153 | base_mip_level: 0, 154 | level_count: mip_levels - base_mip_level, 155 | base_array_layer: 0, 156 | layer_count: array_layers - base_array_layer, 157 | aspects: :color) 158 | access_opts = detect_transition_access_and_stage_flags(from, to) 159 | cmd.pipeline_image_barrier from_layout: from, 160 | to_layout: to, 161 | src_queue_family: src_queue_family, 162 | dst_queue_family: dst_queue_family, 163 | image: self, 164 | aspects: aspects, 165 | base_mip_level: base_mip_level, 166 | level_count: level_count, 167 | base_array_layer: base_array_layer, 168 | layer_count: layer_count, 169 | **access_opts 170 | @layout = to 171 | end 172 | 173 | # Transitions the layout for this image, using the given command pool to 174 | # allocate a command buffer and using the given queue to submit it. If 175 | # `wait_until_idle` is true, this method will block until the queue is 176 | # idle. Otherwise, it will return as soon as the command buffer is 177 | # submitted. See #transition_layout_with for other arguments. 178 | def transition_layout(command_pool, queue, wait_until_idle: true, **other_args) 179 | command_buffer = command_pool.create_command_buffer(usage: :one_time_submit) do |cmd| 180 | transition_layout_using(cmd, **other_args) 181 | end 182 | queue.submit([command_buffer]) 183 | queue.wait_until_idle if wait_until_idle 184 | end 185 | 186 | def copy_from_buffer(command_pool, queue, 187 | buffer:, 188 | buffer_offset: 0, 189 | buffer_row_length: 0, 190 | buffer_image_height: 0, 191 | x: 0, 192 | y: 0, 193 | z: 0, 194 | width: self.width - x, 195 | height: self.height - y, 196 | depth: self.depth - z, 197 | aspects: :color, 198 | mip_level: 0, 199 | base_array_layer: 0, 200 | layer_count: array_layers - base_array_layer, 201 | wait_until_idle: true) 202 | command_buffer = command_pool.create_command_buffer(usage: :one_time_submit) do |cmd| 203 | cmd.copy_buffer_to_image buffer, self, [ 204 | buffer_offset: buffer_offset, 205 | buffer_row_length: buffer_row_length, 206 | buffer_image_height: buffer_image_height, 207 | x: x, 208 | y: y, 209 | z: z, 210 | width: width, 211 | height: height, 212 | depth: depth, 213 | aspects: aspects, 214 | mip_level: mip_level, 215 | base_array_layer: base_array_layer, 216 | layer_count: layer_count 217 | ] 218 | end 219 | 220 | queue.submit([command_buffer]) 221 | queue.wait_until_idle if wait_until_idle 222 | end 223 | 224 | # See CommandBuffer#blit_image for details. If `:wait_until_idle` is true, 225 | # this method won't return until the blit operation has completed and its 226 | # queue is idle. 227 | def blit_from(command_pool, queue, 228 | src:, 229 | src_layout: :transfer_src_optimal, 230 | dst_layout: :transfer_dst_optimal, 231 | regions:, 232 | filter: :nearest, 233 | wait_until_idle: false) 234 | command_buffer = command_pool.create_command_buffer(usage: :one_time_submit) do |cmd| 235 | cmd.blit_image src_image: src, 236 | src_image_layout: src_layout, 237 | dst_image: self, 238 | dst_image_layout: dst_layout, 239 | regions: regions, 240 | filter: filter 241 | end 242 | 243 | queue.submit([command_buffer]) 244 | queue.wait_until_idle if wait_until_idle 245 | end 246 | 247 | def map(*args, &block) 248 | memory.map(*args, &block) 249 | end 250 | 251 | def mapped? 252 | memory.mapped? 253 | end 254 | 255 | def unmap 256 | memory.unmap 257 | end 258 | 259 | def data 260 | memory.data 261 | end 262 | end 263 | end 264 | -------------------------------------------------------------------------------- /lib/vulkan/image_memory.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class ImageMemory < Memory 3 | def initialize(vk, physical_device, owner:, **options) 4 | @owner = owner 5 | super(vk, physical_device, **options) 6 | end 7 | 8 | def query_memory_requirements 9 | VkMemoryRequirements.malloc.tap do |req| 10 | @vk.vkGetImageMemoryRequirements(@vk.device, @owner.to_ptr, req) 11 | end 12 | end 13 | 14 | def bind 15 | @vk.vkBindImageMemory(@vk.device, @owner.to_ptr, to_ptr, 0); 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/vulkan/image_view.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class ImageView 3 | include Vulkan::Checks 4 | include Vulkan::Conversions 5 | include Vulkan::Finalizer 6 | 7 | attr_reader :image 8 | 9 | def initialize(vk, image, image_format, 10 | view_type: VK_IMAGE_VIEW_TYPE_2D, 11 | red_swizzle: VK_COMPONENT_SWIZZLE_IDENTITY, 12 | green_swizzle: VK_COMPONENT_SWIZZLE_IDENTITY, 13 | blue_swizzle: VK_COMPONENT_SWIZZLE_IDENTITY, 14 | alpha_swizzle: VK_COMPONENT_SWIZZLE_IDENTITY, 15 | aspects: :color, 16 | base_mip_level: 0, 17 | level_count: 1, 18 | base_array_layer: 0, 19 | layer_count: 1) 20 | @vk = vk 21 | @image = image 22 | create_info = VkImageViewCreateInfo.malloc 23 | create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO 24 | create_info.image = image.respond_to?(:to_ptr) ? image.to_ptr : image 25 | create_info.viewType = view_type 26 | create_info.format = sym_to_image_format(image_format) 27 | create_info.components.r = red_swizzle 28 | create_info.components.g = green_swizzle 29 | create_info.components.b = blue_swizzle 30 | create_info.components.a = alpha_swizzle 31 | create_info.subresourceRange.aspectMask = syms_to_image_aspect_flags(aspects) 32 | create_info.subresourceRange.baseMipLevel = base_mip_level 33 | create_info.subresourceRange.levelCount = level_count 34 | create_info.subresourceRange.baseArrayLayer = base_array_layer 35 | create_info.subresourceRange.layerCount = layer_count 36 | view_p = Vulkan.create_value("void *", nil) 37 | check_result @vk.vkCreateImageView(vk.device, create_info, nil, view_p) 38 | @handle = view_p.value 39 | finalize_with @vk, :vkDestroyImageView, vk.device, @handle, nil 40 | end 41 | 42 | def to_ptr 43 | @handle 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/vulkan/instance.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class Instance 3 | extend Vulkan::Checks 4 | extend Vulkan::Conversions 5 | include Vulkan::Checks 6 | include Vulkan::Conversions 7 | include Vulkan::Finalizer 8 | 9 | class << self 10 | def extensions 11 | @extensions ||= begin 12 | property_count_ptr = Vulkan.create_value("uint32_t", 0) 13 | check_result Vulkan[nil, nil].vkEnumerateInstanceExtensionProperties(nil, property_count_ptr, nil) 14 | property_count = property_count_ptr.value 15 | container = Vulkan.struct("properties[#{property_count}]" => VkExtensionProperties).malloc 16 | check_result Vulkan[nil, nil].vkEnumerateInstanceExtensionProperties(nil, property_count_ptr, container) 17 | container.properties.map { |prop| struct_to_hash(prop) } 18 | end 19 | end 20 | 21 | def layers 22 | @layers ||= begin 23 | property_count_ptr = Vulkan.create_value("uint32_t", 0) 24 | check_result Vulkan[nil, nil].vkEnumerateInstanceLayerProperties(property_count_ptr, nil) 25 | property_count = property_count_ptr.value 26 | container_struct = Vulkan.struct("properties[#{property_count}]" => VkLayerProperties) 27 | container = container_struct.malloc 28 | check_result Vulkan[nil, nil].vkEnumerateInstanceLayerProperties(property_count_ptr, container) 29 | container.properties.map { |prop| struct_to_hash(prop) } 30 | end 31 | end 32 | 33 | def extension_names 34 | extensions.map { |ext| ext[:extension_name] } 35 | end 36 | 37 | def layer_names 38 | layers.map { |layer| layer[:layer_name] } 39 | end 40 | 41 | def version 42 | ver_p = Vulkan.create_value('uint32_t', 0) 43 | check_result Vulkan[nil, nil].vkEnumerateInstanceVersion(ver_p) 44 | vk_parse_version ver_p.value 45 | end 46 | end 47 | 48 | def initialize(application_name: $0, application_version: '1.0.0', 49 | engine_name: 'vulkan-ruby', engine_version: Vulkan::VERSION, 50 | api_version: self.class.version, extensions: , 51 | layers: []) 52 | layers.concat ENV['LAYERS'].split(/\:\s/) if ENV['LAYERS'] 53 | extensions.concat ENV['INSTANCE_EXTENSIONS'].split(/\:\s/) if ENV['INSTANCE_EXTENSIONS'] 54 | 55 | if ENV['DEBUG'] 56 | extension_names = self.class.extension_names 57 | %w( 58 | VK_EXT_debug_utils 59 | ).each { |ext_name| extensions << ext_name if extension_names.include?(ext_name) } 60 | 61 | layer_names = self.class.layer_names 62 | %w( 63 | VK_LAYER_LUNARG_standard_validation 64 | VK_LAYER_LUNARG_parameter_validation 65 | VK_LAYER_LUNARG_assistant_layer 66 | VK_LAYER_LUNARG_api_dump 67 | VK_LAYER_GOOGLE_unique_objects 68 | VK_LAYER_LUNARG_demo_layer 69 | VK_LAYER_GOOGLE_threading 70 | VK_LAYER_LUNARG_monitor 71 | VK_LAYER_LUNARG_starter_layer 72 | VK_LAYER_LUNARG_core_validation 73 | VK_LAYER_LUNARG_object_tracker 74 | ).each do |layer_name| 75 | layers << layer_name if layer_names.include?(layer_name) 76 | end 77 | end 78 | 79 | extensions_p = Vulkan.struct("names[#{extensions.size}]" => ['char *name']).malloc 80 | extensions.each_with_index do |ext, i| 81 | extname = ext.kind_of?(String) ? ext : ext[:extension_name] 82 | extensions_p.names[i].name = Fiddle::Pointer[extname.b + "\x00"] 83 | end 84 | 85 | layers_p = Vulkan.struct("names[#{layers.size}]" => ['char *name']).malloc 86 | layers.each_with_index do |layer, i| 87 | layer_name = layer.kind_of?(String) ? layer : layer[:layer_name] 88 | layers_p.names[i].name = Fiddle::Pointer[layer_name.b + "\x00"] 89 | end 90 | 91 | application_info = VkApplicationInfo.malloc 92 | application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO 93 | application_info.pNext = nil 94 | application_info.pApplicationName = application_name 95 | application_info.applicationVersion = vk_make_version(application_version) 96 | application_info.pEngineName = 'vulkan-ruby' 97 | application_info.engineVersion = vk_make_version(engine_version) 98 | application_info.apiVersion = vk_make_version(api_version) 99 | 100 | instance_info = VkInstanceCreateInfo.malloc 101 | instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO 102 | instance_info.pNext = nil 103 | instance_info.flags = 0 104 | instance_info.pApplicationInfo = application_info 105 | instance_info.enabledLayerCount = layers.size 106 | instance_info.ppEnabledLayerNames = layers_p 107 | instance_info.enabledExtensionCount = extensions.size 108 | instance_info.ppEnabledExtensionNames = extensions_p 109 | 110 | instance_wrapper = Vulkan.create_value("void *", nil) 111 | check_result Vulkan[nil, nil].vkCreateInstance(instance_info, nil, instance_wrapper) 112 | @handle = instance_wrapper.value 113 | @vk = Vulkan[self, nil] 114 | hook_debug_utils_callback if extensions.include?('VK_EXT_debug_utils') 115 | finalize_with @vk, :vkDestroyInstance, @handle, nil 116 | end 117 | 118 | def finalized=(b) 119 | @finalized = b 120 | end 121 | 122 | def finalized? 123 | !!@finalized 124 | end 125 | 126 | def to_i 127 | to_ptr.to_i 128 | end 129 | 130 | def on_log(&cb) 131 | @log_callback = cb 132 | end 133 | 134 | def hook_debug_utils_callback 135 | _, return_type, param_types = Vulkan.parse_signature('VkBool32 debug_callback(int messageSeverity,' + 136 | 'int messageType,' + 137 | 'void *pCallbackData,' + 138 | 'void *pUserData)') 139 | @debug_util_callback = Fiddle::Closure::BlockCaller.new(return_type, param_types) do |msg_severity, msg_type, cb_data_addr, user_arg_addr| 140 | data = VkDebugUtilsMessengerCallbackDataEXT.new(cb_data_addr) 141 | type = const_to_symbol(msg_type, /^VK_DEBUG_UTILS_MESSAGE_TYPE_(.*?)_BIT_EXT$/) 142 | severity = const_to_symbol(msg_severity, /^VK_DEBUG_UTILS_MESSAGE_SEVERITY_(.*?)_BIT_EXT$/) 143 | if @log_callback 144 | @log_callback.call severity, type, data.pMessage.to_s 145 | else 146 | puts ['[UTIL]', "[#{severity}]", "[#{type}]", data.pMessage.to_s].join("\t") 147 | end 148 | VK_FALSE # don't bail 149 | end 150 | 151 | create_info = VkDebugUtilsMessengerCreateInfoEXT.malloc 152 | create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT 153 | create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | 154 | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | 155 | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT 156 | create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | 157 | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | 158 | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT 159 | create_info.pUserData = nil 160 | create_info.pfnUserCallback = @debug_util_callback 161 | 162 | callback_p = Vulkan.create_value('void *', nil) 163 | check_result @vk.vkCreateDebugUtilsMessengerEXT(to_ptr, create_info, nil, callback_p) 164 | @debug_util_callback_handle = callback_p.value 165 | finalize_with @vk, :vkDestroyDebugUtilsMessengerEXT, to_ptr, @debug_util_callback_handle, nil 166 | end 167 | 168 | def create_window_surface(window) 169 | WindowSurface.new(self, window) 170 | end 171 | 172 | # Returns an array of physical device handles. Use these to query the 173 | # capabilities of each physical device, and to create logical devices 174 | # based on the results. 175 | def physical_device_handles 176 | @physical_device_handles ||= begin 177 | # get device count 178 | device_count_ptr = Vulkan.create_value("uint32_t", 0) 179 | # check_result func.call(@handle, device_count_ptr, nil) 180 | check_result @vk.vkEnumeratePhysicalDevices(@handle, device_count_ptr, nil) 181 | device_count = device_count_ptr.value 182 | # allocate n devices 183 | container_struct = Vulkan.struct("handles[#{device_count}]" => ['VkPhysicalDevice handle']) 184 | container = container_struct.malloc 185 | # check_result func.call(@handle, device_count_ptr, container) 186 | check_result @vk.vkEnumeratePhysicalDevices(@handle, device_count_ptr, container) 187 | container.handles 188 | end 189 | end 190 | 191 | def physical_devices 192 | @physical_devices ||= physical_device_handles.map { |dev| PhysicalDevice.new(self, dev.handle) } 193 | end 194 | end 195 | end 196 | -------------------------------------------------------------------------------- /lib/vulkan/logical_device.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class LogicalDevice 3 | include Vulkan::Checks 4 | include Vulkan::Conversions 5 | include Vulkan::Finalizer 6 | 7 | attr_reader :physical_device 8 | attr_reader :queue_families 9 | attr_reader :enabled_features 10 | 11 | def initialize(instance, physical_device, queues:, extensions:, features: physical_device.supported_features) 12 | raise ArgumentError, "instance can't be nil" unless instance 13 | raise ArgumentError, "physical_device can't be nil" unless physical_device 14 | @instance = instance 15 | @physical_device = physical_device 16 | 17 | extensions.concat ENV['DEVICE_EXTENSIONS'].split(/\:\s/) if ENV['DEVICE_EXTENSIONS'] 18 | extensions << 'VK_KHR_portability_subset' if physical_device.extension_names.include?('VK_KHR_portability_subset') 19 | 20 | if queues.size == 0 21 | # take the first available queue, to satisfy the spec (must request a queue) 22 | queues = [{ family: physical_device.queue_families.first, priorities: [1.0] }] 23 | end 24 | 25 | queues_p = queues.each_with_index.map do |queue_info, index| 26 | family_index = queue_family_to_index(queue_info[:family]) 27 | priorities = queue_info[:priorities] || raise(ArgumentError, 'queue :priorities (array of floats) is required') 28 | 29 | priorities = [1.0] if priorities.size == 0 30 | queue_priorities_p = Fiddle::Pointer.malloc(Fiddle::SIZEOF_FLOAT * priorities.size) 31 | priorities.each_with_index do |priority, i| 32 | queue_priorities_p[i * Fiddle::SIZEOF_FLOAT, Fiddle::SIZEOF_FLOAT] = [priority].pack(Fiddle::PackInfo::PACK_MAP[Fiddle::TYPE_FLOAT]) 33 | end 34 | 35 | device_queue_info = VkDeviceQueueCreateInfo.malloc 36 | device_queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO 37 | device_queue_info.pNext = nil 38 | device_queue_info.flags = 0 39 | device_queue_info.queueFamilyIndex = family_index 40 | device_queue_info.queueCount = priorities.size 41 | device_queue_info.pQueuePriorities = queue_priorities_p 42 | device_queue_info 43 | end 44 | queue_infos_p = array_of_structures(queues_p) 45 | 46 | extensions_p = Vulkan.struct("names[#{extensions.size}]" => ['char *name']).malloc 47 | extensions.each_with_index do |ext, i| 48 | extname = ext.kind_of?(String) ? ext : ext[:extension_name] 49 | extensions_p.names[i].name = Fiddle::Pointer[extname.b + "\x00"] 50 | end 51 | 52 | enabled_features = VkPhysicalDeviceFeatures.malloc 53 | enabled_features.to_ptr.copy_from physical_device.features.to_ptr 54 | enabled_features.class.members.each do |member| 55 | member_name = member.gsub(/[0-9A-Z]+/) { |x| "_#{x.downcase}" }.to_sym 56 | if features.include?(member_name) 57 | if enabled_features[member] == VK_FALSE 58 | raise Error::UnsupportedFeature.new(member_name, member) 59 | end 60 | else 61 | enabled_features[member] = VK_FALSE 62 | end 63 | end 64 | @enabled_features = struct_to_hash(enabled_features).reject! { |name, enabled| enabled != VK_TRUE }.keys 65 | 66 | device_create_info = VkDeviceCreateInfo.malloc 67 | device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO 68 | device_create_info.pNext = nil 69 | device_create_info.flags = 0 70 | device_create_info.queueCreateInfoCount = queues.size 71 | device_create_info.pQueueCreateInfos = queue_infos_p 72 | device_create_info.enabledLayerCount = 0 # deprecated 73 | device_create_info.ppEnabledLayerNames = nil 74 | device_create_info.enabledExtensionCount = extensions.size 75 | device_create_info.ppEnabledExtensionNames = extensions_p 76 | device_create_info.pEnabledFeatures = enabled_features 77 | 78 | device_wrapper = Vulkan.create_value("void *", nil) 79 | check_result Vulkan[instance, nil].vkCreateDevice(physical_device.to_ptr, device_create_info, nil, device_wrapper) 80 | @handle = device_wrapper.value 81 | @vk = Vulkan[instance, self] 82 | finalize_with Vulkan[instance, nil], :vkDestroyDevice, @handle, nil 83 | 84 | queue_handle_wrapper = Vulkan.create_value("void *", nil) 85 | @queue_families = queues.map do |queue| 86 | queues = queue[:priorities].each_with_index.map do |priority, index| 87 | @vk.vkGetDeviceQueue(@handle, queue_family_to_index(queue[:family]), index, queue_handle_wrapper) 88 | Vulkan::Queue.new(@vk, queue_handle_wrapper.value, index: index, priority: priority) 89 | end 90 | queue[:family].merge queues: queues 91 | end 92 | end 93 | 94 | def finalized=(b) 95 | @finalized = b 96 | end 97 | 98 | def finalized? 99 | !!@finalized 100 | end 101 | 102 | def to_i 103 | to_ptr.to_i 104 | end 105 | 106 | def max_samples 107 | physical_device.max_samples 108 | end 109 | 110 | def max_color_samples 111 | physical_device.max_color_samples 112 | end 113 | 114 | def max_depth_samples 115 | physical_device.max_depth_samples 116 | end 117 | 118 | def feature_enabled?(feature_name) 119 | @enabled_features.include?(feature_name) 120 | end 121 | 122 | def create_buffer(**args) 123 | Vulkan::Buffer.new(@vk, physical_device, **args) 124 | end 125 | 126 | def create_shader_stage(**args) 127 | Vulkan::ShaderStage.new(@vk, **args) 128 | end 129 | 130 | def create_fence(**args) 131 | Vulkan::Fence.new(@vk, **args) 132 | end 133 | 134 | def create_semaphore 135 | Vulkan::Semaphore.new(@vk) 136 | end 137 | 138 | def create_pipeline(**args) 139 | Vulkan::Pipeline.new(@vk, **args) 140 | end 141 | 142 | def create_swapchain(**args) 143 | Vulkan::Swapchain.new(@instance, self, **args) 144 | end 145 | 146 | def create_renderpass 147 | Vulkan::RenderPass.new(@vk) 148 | end 149 | 150 | def create_command_pool(**args) 151 | Vulkan::CommandPool.new(@vk, **args) 152 | end 153 | 154 | def create_descriptor_set_pool(**args) 155 | Vulkan::DescriptorPool.new(@vk, **args) 156 | end 157 | 158 | def create_image(**args) 159 | Vulkan::Image.new(@vk, physical_device, **args) 160 | end 161 | 162 | def create_sampler(**args) 163 | Vulkan::Sampler.new(@vk, self, **args) 164 | end 165 | 166 | def create_framebuffer(**args) 167 | Vulkan::Framebuffer.new(@vk, **args) 168 | end 169 | 170 | def create_buffer_memory_barrier(**args) 171 | Vulkan::BufferMemoryBarrier.new(**args) 172 | end 173 | 174 | def create_memory(**args) 175 | Vulkan::Memory.new(@vk, physical_device, **args) 176 | end 177 | 178 | def create_memory_barrier(**args) 179 | Vulkan::MemoryBarrier.new(**args) 180 | end 181 | 182 | def hexaddr 183 | to_ptr.to_i.to_s(16) 184 | end 185 | 186 | def to_ptr 187 | @handle 188 | end 189 | end 190 | end 191 | -------------------------------------------------------------------------------- /lib/vulkan/manual_types.rb: -------------------------------------------------------------------------------- 1 | require 'fiddle/import' 2 | require 'fiddle/types' 3 | 4 | module Vulkan 5 | # Defines types manually that could not be auto generated from vk.xml. 6 | # A type defined here will be skipped if encountered while processing 7 | # vk.xml. 8 | module ManualTypes 9 | def self.included(base) 10 | base.module_eval do 11 | typealias 'int8_t', 'char' 12 | typealias 'int16_t', 'short' 13 | typealias 'int32_t', 'int' 14 | typealias 'int64_t', 'long' 15 | typealias 'uint8_t', 'unsigned char' 16 | typealias 'uint16_t', 'unsigned short' 17 | typealias 'uint32_t', 'unsigned int' 18 | typealias 'uint64_t', 'unsigned long' 19 | 20 | # platform-specific definitions, but we need them even on alternatives, 21 | # or else things get complicated when parsing vk.xml 22 | # X11 23 | typealias 'Display', 'void' # HACK: since only pointers to this type are used, its size need not be known 24 | typealias 'VisualID', 'unsigned long' 25 | typealias 'XID', 'unsigned long' 26 | typealias 'Window', 'XID' 27 | typealias 'RROutput', 'XID' 28 | # Wayland 29 | typealias 'wl_display', 'void' # HACK: since only pointers to this type are used, its size need not be known 30 | typealias 'wl_surface', 'void' # HACK: since only pointers to this type are used, its size need not be known 31 | # Windows 32 | include Fiddle::Win32Types 33 | typealias 'WCHAR', 'unsigned short' 34 | typealias 'LPCWSTR', 'WCHAR *' 35 | # XCB 36 | typealias 'xcb_connection_t', 'void' # HACK: since only pointers to this type are used, its size need not be known 37 | typealias 'xcb_visualid_t', 'uint32_t' 38 | typealias 'xcb_window_t', 'uint32_t' 39 | # Zircon 40 | typealias 'zx_handle_t', 'uint32_t' 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/vulkan/memory.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class Memory 3 | include Vulkan::Checks 4 | include Vulkan::Conversions 5 | include Vulkan::Finalizer 6 | 7 | attr_reader :requirements 8 | attr_reader :physical_device 9 | 10 | def initialize(vk, physical_device, properties: [:host_visible]) 11 | @range_array = nil 12 | @vk = vk 13 | @physical_device = physical_device 14 | @properties = syms_to_memory_properties(properties) 15 | @mapped = Vulkan.create_value('void *', nil) 16 | 17 | @requirements = query_memory_requirements 18 | 19 | alloc_info = VkMemoryAllocateInfo.malloc 20 | alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO 21 | alloc_info.allocationSize = @requirements.size 22 | alloc_info.memoryTypeIndex = memory_type_index 23 | memory_handle_p = Vulkan.create_value('void *', nil) 24 | check_result @vk.vkAllocateMemory(@vk.device, alloc_info, nil, memory_handle_p) 25 | @handle = memory_handle_p.value 26 | finalize_with @vk, :vkFreeMemory, @vk.device, @handle, nil 27 | bind 28 | end 29 | 30 | def size 31 | @requirements.size 32 | end 33 | 34 | def memory_type_index 35 | properties = {} 36 | physical_device.memory_properties[:memory_type_count].times do |i| 37 | if (requirements.memoryTypeBits & (1 << i)) != 0 38 | if (physical_device.memory_properties[:memory_types][i][:property_flags] & @properties) == @properties 39 | return i 40 | else 41 | properties[i] = "0x%x" % physical_device.memory_properties[:memory_types][i][:property_flags] 42 | end 43 | end 44 | end 45 | 46 | raise 'failed to find suitable memory type (needed properties == 0x%x, memory type count == %d, requrement memory type bits == 0b%s, available properties == %s)' % [ 47 | @properties, 48 | physical_device.memory_properties[:memory_type_count], 49 | requirements.memoryTypeBits.to_s(2), 50 | properties.inspect 51 | ] 52 | end 53 | 54 | def data 55 | raise 'no data pointer: buffer is not mapped' unless mapped? 56 | @mapped 57 | end 58 | 59 | def mapped? 60 | @mapped && @mapped.value.to_i != 0 61 | end 62 | 63 | def unmap 64 | raise 'buffer is not mapped' unless mapped? 65 | @mapped.value = nil 66 | @vk.vkUnmapMemory(@vk.device, @handle) 67 | end 68 | 69 | # Flushes a single range of memory, which defaults to the entire buffer. 70 | # Do this after modifying memory to inform the device that it may have 71 | # changed. This method is called automatically after the block form of 72 | # #map has completed, but is not called automatically if no block is 73 | # provided to #map. 74 | # 75 | # See https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkFlushMappedMemoryRanges.html 76 | def flush(offset: 0, size: offset > 0 ? self.size - offset : VK_WHOLE_SIZE) 77 | flush_all [{ offset: offset, size: size }] 78 | end 79 | 80 | # Flushes each of a set of memory ranges, given as 81 | # `[{:offset => number, :size => number}]`. No defaults are provided, 82 | # and ArgumentError will be raised if any fields are omitted. 83 | # Do this after modifying memory to inform the device that it may have 84 | # changed. 85 | # 86 | # See https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkFlushMappedMemoryRanges.html 87 | def flush_all(ranges) 88 | @vk.vkFlushMappedMemoryRanges(@vk.device, ranges.size, memory_ranges(ranges)) 89 | end 90 | 91 | # Invalidates a single range of memory, which defaults to the entire 92 | # buffer. Do this prior to reading memory that may have been modified by 93 | # a device. This method is called automatically at the beginning of #map, 94 | # but is not called automatically at any other point, so if you leave 95 | # the memory mapped (for example, by not passing a block to #map), then 96 | # you need to call this method explicitly at the appropriate times. 97 | # 98 | # See https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkInvalidateMappedMemoryRanges.html 99 | def invalidate(offset: 0, size: offset > 0 ? self.size - offset : VK_WHOLE_SIZE) 100 | invalidate_all [{ offset: offset, size: size }] 101 | end 102 | 103 | # Invalidates each of a set of memory ranges, given as 104 | # `[{:offset => number, :size => number}]`. No defaults are provided, 105 | # and ArgumentError will be raised if any fields are omitted. Do this 106 | # prior to reading memory that may have been modified by a device. 107 | # 108 | # See https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/vkInvalidateMappedMemoryRanges.html 109 | def invalidate_all(ranges) 110 | if mapped? 111 | @vk.vkInvalidateMappedMemoryRanges(@vk.device, ranges.size, memory_ranges(ranges)) 112 | end 113 | end 114 | 115 | # Provides a pointer to an array of memory ranges for e.g. flushing memory 116 | protected def memory_ranges(ranges) 117 | # avoid reallocating @range_array if possible 118 | unless @range_array&.ranges&.count.to_i >= ranges.count 119 | @range_array = Vulkan.struct("ranges[#{ranges.count}]" => VkMappedMemoryRange).malloc 120 | ranges.each_with_index do |rng, i| 121 | range = @range_array.ranges[i] 122 | range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE 123 | range.pNext = nil 124 | range.memory = @handle 125 | end 126 | end 127 | ranges.each_with_index do |rng, i| 128 | range = @range_array.ranges[i] 129 | range.offset = rng[:offset] || raise(ArgumentError, "memory range must specify :offset") 130 | range.size = rng[:size] || raise(ArgumentError, "memory range must specify :size") 131 | end 132 | @range_array 133 | end 134 | 135 | # Maps this memory range. If a block is provided, a pointer to the start 136 | # of the mapped memory range is yielded to the block and the memory is 137 | # automatically unmapped after the block finishes and `nil` is returned 138 | # to prevent writing to a no-longer-mapped address. 139 | # 140 | # If no block is provided, the memory remains mapped and the pointer is 141 | # returned. 142 | # 143 | # If `invalidate` is true, the range to be mapped will be invalidated 144 | # automatically before mapping to ensure that the memory will be up to 145 | # date with any changes from the device. If false, the memory may be out 146 | # of date when you map it. 147 | # 148 | # If `flush` is true, and a block is given, then the memory will be 149 | # flushed to ensure that it is updated on the device after your block 150 | # completes. If no block is given, this option has no effect. 151 | def map(offset: 0, size: offset > 0 ? self.size - offset : VK_WHOLE_SIZE, 152 | invalidate: true, flush: true, &block) 153 | raise 'buffer is already mapped' if mapped? 154 | self.invalidate offset: offset, size: size if invalidate 155 | @vk.vkMapMemory(@vk.device, @handle, offset, size, 0, @mapped) 156 | size = self.size if size == VK_WHOLE_SIZE 157 | if block_given? 158 | begin 159 | yield Fiddle::Pointer.new(@mapped.value.to_i, size) 160 | ensure 161 | self.flush offset: offset, size: size if flush 162 | unmap 163 | end 164 | return nil 165 | else 166 | return Fiddle::Pointer.new(@mapped.value.to_i, size) 167 | end 168 | end 169 | end 170 | end 171 | -------------------------------------------------------------------------------- /lib/vulkan/memory_barrier.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class MemoryBarrier 3 | include Vulkan::Conversions 4 | 5 | def initialize(src_access:, 6 | dst_access:) 7 | @struct = VkMemoryBarrier.malloc 8 | @struct.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER 9 | self.src_access = src_access 10 | self.dst_access = dst_access 11 | end 12 | 13 | def src_access; @src_access; end 14 | def src_access=(s); @src_access = s; @struct.srcAccessMask = syms_to_access_mask(s); end 15 | def dst_access; @dst_access; end 16 | def dst_access=(s); @dst_access = s; @struct.dstAccessMask = syms_to_access_mask(s); end 17 | 18 | def to_ptr 19 | @struct.to_ptr 20 | end 21 | end 22 | end 23 | 24 | -------------------------------------------------------------------------------- /lib/vulkan/mock.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | module Mock 3 | autoload :SwapchainSurfaceInfo, 'vulkan/mock/swapchain_surface_info' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/vulkan/mock/swapchain_surface_info.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | module Mock 3 | # A mock object that you can use in your unit testing. This way you can 4 | # construct Vulkan-ish data for testing against that doesn't depend on any 5 | # underlying hardware. All of the properties of this object are optional, 6 | # and sane (but arbitary) defaults will be provided for anything omitted. 7 | class SwapchainSurfaceInfo 8 | # These values were chosen arbitrarily. 9 | DEFAULT_CAPABILITIES = { 10 | min_image_count: 0, 11 | max_image_count: 10, 12 | current_extent: { width: 800, height: 600 }, 13 | min_image_extent: { width: 1, height: 1 }, 14 | max_image_extent: { width: 8192, height: 8192 }, 15 | max_image_array_layers: 128, 16 | supported_transforms: [ :identity, :rotate_90, :rotate_180, 17 | :rotate_270, :horizontal_mirror, 18 | :horizontal_mirror_rotate_90, 19 | :horizontal_mirror_rotate_180, 20 | :horizontal_mirror_rotate_270, 21 | :inherit ], 22 | current_transform: [ :identity ], 23 | supported_composite_alpha: [ :opaque, :pre_multiplied, :post_multiplied, :inherit ], 24 | supported_usage: [ :transfer_src, :transfer_dst, :sampled, :storage, 25 | :color_attachment, :depth_stencil_attachment, 26 | :transient_attachment, :input_attachment ] 27 | } 28 | 29 | attr_reader :presentation_modes 30 | attr_reader :formats 31 | attr_reader :capabilities 32 | 33 | def initialize(presentation_modes: [:fifo, :mailbox, :immediate], 34 | formats: [{ format: :r8g8b8a8_snorm, color_space: :srgb_nonlinear }], 35 | **capabilities) 36 | @presentation_modes = presentation_modes 37 | @formats = formats 38 | @capabilities = DEFAULT_CAPABILITIES.merge(capabilities) 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/vulkan/physical_device.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class PhysicalDevice 3 | include Vulkan::Checks 4 | include Vulkan::Conversions 5 | 6 | def initialize(instance, handle) 7 | @handle = handle 8 | @instance = instance 9 | @vk = Vulkan[instance, nil] 10 | end 11 | 12 | def inspect 13 | # force lazy instance variables to be initialized 14 | to_hash 15 | super 16 | end 17 | 18 | def to_ptr 19 | @handle 20 | end 21 | 22 | def to_hash 23 | { 24 | extensions: extensions, 25 | properties: properties, 26 | features: features_hash, 27 | queue_families: queue_families 28 | } 29 | end 30 | 31 | def supported_features 32 | struct_to_hash(features).reject! { |k, v| v != VK_TRUE }.keys 33 | end 34 | 35 | def unsupported_features 36 | struct_to_hash(features).reject! { |k, v| v == VK_TRUE }.keys 37 | end 38 | 39 | def max_samples 40 | [max_color_samples, max_depth_samples].min 41 | end 42 | 43 | def sample_counts_to_max(counts) 44 | return VK_SAMPLE_COUNT_64_BIT if counts & VK_SAMPLE_COUNT_64_BIT != 0 45 | return VK_SAMPLE_COUNT_32_BIT if counts & VK_SAMPLE_COUNT_32_BIT != 0 46 | return VK_SAMPLE_COUNT_16_BIT if counts & VK_SAMPLE_COUNT_16_BIT != 0 47 | return VK_SAMPLE_COUNT_8_BIT if counts & VK_SAMPLE_COUNT_8_BIT != 0 48 | return VK_SAMPLE_COUNT_4_BIT if counts & VK_SAMPLE_COUNT_4_BIT != 0 49 | return VK_SAMPLE_COUNT_2_BIT if counts & VK_SAMPLE_COUNT_2_BIT != 0 50 | return VK_SAMPLE_COUNT_1_BIT 51 | end 52 | 53 | def max_color_samples 54 | sample_counts_to_max properties[:limits][:framebuffer_color_sample_counts] 55 | end 56 | 57 | def max_depth_samples 58 | sample_counts_to_max properties[:limits][:framebuffer_depth_sample_counts] 59 | end 60 | 61 | def detect_supported_format(*candidates, usage:, tiling: :optimal) 62 | usage = syms_to_format_feature_flags(usage) 63 | tiling = sym_to_image_tiling(tiling) 64 | candidates.flatten.each do |candidate| 65 | props = VkFormatProperties.malloc 66 | @vk.vkGetPhysicalDeviceFormatProperties(to_ptr, sym_to_format(candidate), props) 67 | if tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & usage) == usage 68 | return candidate 69 | elsif tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & usage) == usage 70 | return candidate 71 | end 72 | end 73 | nil 74 | end 75 | 76 | # Returns the swapchain surface info if the `"VK_KHR_swapchain"` extension 77 | # is supported, `nil` otherwise. 78 | def swapchain_surface_info(surface) 79 | if extension_names.include?('VK_KHR_swapchain') 80 | SwapchainSurfaceInfo.new(@instance, self, surface) 81 | else 82 | nil 83 | end 84 | end 85 | 86 | def extension_names 87 | extensions.map { |ext| ext[:extension_name] } 88 | end 89 | 90 | def create_logical_device(**args) 91 | Vulkan::LogicalDevice.new(@instance, self, **args) 92 | end 93 | 94 | alias create create_logical_device 95 | 96 | def memory_properties 97 | @memory_properties ||= begin 98 | memory_properties = VkPhysicalDeviceMemoryProperties.malloc 99 | @vk.vkGetPhysicalDeviceMemoryProperties(to_ptr, memory_properties) 100 | struct_to_hash(memory_properties) 101 | end 102 | end 103 | 104 | def queue_families 105 | @queue_families ||= begin 106 | count_ptr = Vulkan.create_value("uint32_t", 0) 107 | @vk.vkGetPhysicalDeviceQueueFamilyProperties(@handle, count_ptr, nil) 108 | 109 | container_struct = Vulkan.struct("queues[#{count_ptr.value}]" => VkQueueFamilyProperties) 110 | container = container_struct.malloc 111 | @vk.vkGetPhysicalDeviceQueueFamilyProperties(@handle, count_ptr, container) 112 | container.queues.each_with_index.map do |prop, index| 113 | info = struct_to_hash(prop, Vulkan::QueueFamily.new(@vk.instance, self, index)) 114 | info[:supports] = flags_to_symbols(info[:queue_flags], /^VK_QUEUE_(.*?)_BIT$/) 115 | info[:index] = index 116 | info[:queues] = [1.0] 117 | info 118 | end 119 | end 120 | end 121 | 122 | def supports_feature?(feature_name) 123 | features_hash[feature_name] == VK_TRUE 124 | end 125 | 126 | def features_hash 127 | struct_to_hash features 128 | end 129 | 130 | def features 131 | @features ||= begin 132 | features = VkPhysicalDeviceFeatures.malloc 133 | @vk.vkGetPhysicalDeviceFeatures(@handle, features) 134 | features 135 | end 136 | end 137 | 138 | def properties 139 | @properties ||= begin 140 | properties = VkPhysicalDeviceProperties.malloc 141 | @vk.vkGetPhysicalDeviceProperties(@handle, properties) 142 | properties = struct_to_hash(properties) 143 | properties[:device_type] = const_to_symbol(properties[:device_type], /^VK_PHYSICAL_DEVICE_TYPE_(.*?)$/) 144 | properties 145 | end 146 | end 147 | 148 | def extensions 149 | @extensions ||= begin 150 | # get properties count 151 | count_ptr = Vulkan.create_value("uint32_t", 0) 152 | check_result @vk.vkEnumerateDeviceExtensionProperties(@handle, nil, count_ptr, nil) 153 | count = count_ptr.value 154 | # allocate n devices 155 | container_struct = Vulkan.struct("handles[#{count}]" => VkExtensionProperties) 156 | container = container_struct.malloc 157 | check_result @vk.vkEnumerateDeviceExtensionProperties(@handle, nil, count_ptr, container) 158 | container.handles.map { |handle| struct_to_hash(handle) } 159 | end 160 | end 161 | end 162 | end 163 | -------------------------------------------------------------------------------- /lib/vulkan/platform.rb: -------------------------------------------------------------------------------- 1 | require 'fiddle' 2 | require 'rbconfig' 3 | 4 | module Vulkan 5 | module Platform 6 | def calling_convention_human 7 | case calling_convention 8 | when Fiddle::Function::STDCALL then :stdcall 9 | when Fiddle::Function::DEFAULT then :default 10 | else raise "BUG: can't identify calling convention???" 11 | end 12 | rescue NameError 13 | :default 14 | end 15 | 16 | def calling_convention 17 | case Vulkan.os 18 | when :windows then Fiddle::Function::STDCALL 19 | else Fiddle::Function::DEFAULT 20 | end 21 | rescue NameError 22 | Fiddle::Function::DEFAULT 23 | end 24 | 25 | def os 26 | case RbConfig::CONFIG['host_os'] 27 | when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ then :windows 28 | when /darwin|mac os/ then :osx 29 | when /linux/, /solaris|bsd/ then :linux 30 | else raise 'could not determine vulkan library to load for this OS' 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/vulkan/queue.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class Queue 3 | include Vulkan::Checks 4 | include Vulkan::Conversions 5 | 6 | attr_reader :priority 7 | attr_reader :index 8 | 9 | def initialize(vk, handle, priority:, index:) 10 | @vk = vk 11 | @handle = handle 12 | @priority = priority 13 | @index = index 14 | 15 | # pointers used and reused during queue submit and presentment, to avoid 16 | # unnecessary GC 17 | @wait_semaphores_buffers = { 0 => nil } 18 | @wait_stages_buffers = { 0 => nil } 19 | @signal_semaphores_buffers = { 0 => nil } 20 | @command_buffers_buffers = { 0 => nil } 21 | @submit_infos = { 0 => nil, 1 => Vulkan.struct("infos[1]" => VkSubmitInfo).malloc } 22 | @submit_infos[1].infos[0].sType = VK_STRUCTURE_TYPE_SUBMIT_INFO 23 | @image_indices_buffers = { 0 => nil } 24 | @swapchains_buffers = { 0 => nil } 25 | @present_info = VkPresentInfoKHR.malloc 26 | @present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR 27 | end 28 | 29 | def submit_infos(submit_infos, fence = nil) 30 | submit_infos_p = @submit_infos[submit_infos.size] ||= Vulkan.struct("infos[#{submit_infos.size}]" => VkSubmitInfo).malloc 31 | submit_infos.each_with_index do |info, i| 32 | next if submit_infos_p.infos[i] == info 33 | submit_infos_p.infos[i].copy_from(info) 34 | end 35 | 36 | check_result @vk.vkQueueSubmit(to_ptr, submit_infos.size, submit_infos_p, fence) 37 | end 38 | 39 | def wait_until_idle 40 | @vk.vkQueueWaitIdle(to_ptr) 41 | end 42 | 43 | def submit(command_buffers, wait_semaphores: [], wait_stages: [], signal_semaphores: [], fence: nil) 44 | wait_stages_p = @wait_stages_buffers[wait_stages.size] ||= Vulkan.struct(["VkPipelineStageFlags stages[#{wait_stages.size}]"]).malloc 45 | wait_stages.each_with_index { |stage, index| wait_stages_p.stages[index] = stage } 46 | 47 | wait_semaphores_p = @wait_semaphores_buffers[wait_semaphores.size] ||= Fiddle::Pointer.malloc(wait_semaphores.size * Fiddle::SIZEOF_VOIDP) 48 | array_of_pointers(wait_semaphores, wait_semaphores_p) 49 | 50 | signal_semaphores_p = @signal_semaphores_buffers[signal_semaphores.size] ||= Fiddle::Pointer.malloc(signal_semaphores.size * Fiddle::SIZEOF_VOIDP) 51 | array_of_pointers(signal_semaphores, signal_semaphores_p) 52 | 53 | command_buffers_p = @command_buffers_buffers[command_buffers.size] ||= Fiddle::Pointer.malloc(command_buffers.size * Fiddle::SIZEOF_VOIDP) 54 | array_of_pointers(command_buffers, command_buffers_p) 55 | 56 | submit_info = @submit_infos[1].infos[0] 57 | submit_info.waitSemaphoreCount = wait_semaphores.size 58 | submit_info.pWaitSemaphores = wait_semaphores_p 59 | submit_info.pWaitDstStageMask = wait_stages_p 60 | submit_info.commandBufferCount = command_buffers.size 61 | submit_info.pCommandBuffers = command_buffers_p 62 | submit_info.signalSemaphoreCount = signal_semaphores.size 63 | submit_info.pSignalSemaphores = signal_semaphores_p 64 | 65 | submit_infos(@submit_infos[1].infos, fence) 66 | end 67 | 68 | def present(swapchains:, image_indices:, wait_semaphores: []) 69 | raise ArgumentError, "swapchains and image_indices must have same sizes" unless swapchains.size == image_indices.size 70 | 71 | wait_semaphores_p = @wait_semaphores_buffers[wait_semaphores.size] ||= Fiddle::Pointer.malloc(wait_semaphores.size * Fiddle::SIZEOF_VOIDP) 72 | array_of_pointers(wait_semaphores, wait_semaphores_p) 73 | 74 | image_indices_p = @image_indices_buffers[image_indices.size] ||= Vulkan.struct(["uint32_t indices[#{image_indices.size}]"]).malloc 75 | data = image_indices.pack("I*") 76 | image_indices_p[0, data.size] = data 77 | 78 | swapchains_p = @swapchains_buffers[swapchains.size] ||= Fiddle::Pointer.malloc(swapchains.size * Fiddle::SIZEOF_VOIDP) 79 | array_of_pointers(swapchains, swapchains_p) 80 | 81 | @present_info.waitSemaphoreCount = wait_semaphores.size 82 | @present_info.pWaitSemaphores = wait_semaphores_p 83 | @present_info.swapchainCount = swapchains.size 84 | @present_info.pSwapchains = swapchains_p 85 | @present_info.pImageIndices = image_indices_p 86 | @present_info.pResults = nil 87 | check_result @vk.vkQueuePresentKHR(to_ptr, @present_info) 88 | end 89 | 90 | def to_ptr 91 | @handle 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /lib/vulkan/queue_family.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class QueueFamily < Hash 3 | include Vulkan::Checks 4 | 5 | attr_reader :physical_device 6 | 7 | def initialize(instance, physical_device, family_index) 8 | super() 9 | @physical_device = physical_device 10 | @vk = Vulkan[instance, nil] 11 | @family_index = family_index 12 | end 13 | 14 | def supports?(feature) 15 | self[:supports].include?(feature) 16 | end 17 | 18 | def supports_presentation?(surface) 19 | supported_p = Vulkan.create_value('VkBool32', 0) 20 | check_result @vk.vkGetPhysicalDeviceSurfaceSupportKHR(@physical_device.to_ptr, 21 | @family_index, 22 | surface.to_ptr, 23 | supported_p) 24 | return supported_p.value == VK_TRUE 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/vulkan/render_pass.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class RenderPass 3 | include Vulkan::Checks 4 | include Vulkan::Conversions 5 | include Vulkan::Finalizer 6 | 7 | attr_reader :attachments 8 | 9 | def initialize(vk) 10 | @vk = vk 11 | @attachments = [] 12 | @subpasses = [] 13 | end 14 | 15 | def to_ptr 16 | super || raise(RuntimeError, 'call #commit first') 17 | end 18 | 19 | def commit 20 | render_pass_info = VkRenderPassCreateInfo.malloc 21 | render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO 22 | render_pass_info.attachmentCount = @attachments.size 23 | render_pass_info.pAttachments = array_of_structures(@attachments) 24 | render_pass_info.subpassCount = @subpasses.size 25 | render_pass_info.pSubpasses = array_of_structures(@subpasses) 26 | 27 | dependency = VkSubpassDependency.malloc 28 | dependency.srcSubpass = VK_SUBPASS_EXTERNAL 29 | dependency.dstSubpass = 0 30 | dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT 31 | dependency.srcAccessMask = 0 32 | dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT 33 | dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT 34 | 35 | render_pass_info.dependencyCount = 1 36 | render_pass_info.pDependencies = dependency 37 | 38 | render_pass_p = Vulkan.create_value('void *', nil) 39 | check_result @vk.vkCreateRenderPass(@vk.device, render_pass_info, nil, render_pass_p) 40 | @handle = render_pass_p.value 41 | finalize_with @vk, :vkDestroyRenderPass, @vk.device, @handle, nil 42 | end 43 | 44 | def add_attachment(format:, 45 | samples: 1, 46 | load_op: :clear, 47 | store_op: :store, 48 | stencil_load_op: :dont_care, 49 | stencil_store_op: :dont_care, 50 | initial_layout: nil, 51 | final_layout: :presentation_src) 52 | attachment = VkAttachmentDescription.malloc 53 | attachment.format = sym_to_image_format(format) 54 | attachment.samples = num_to_samples(samples) 55 | attachment.loadOp = sym_to_load_op(load_op) 56 | attachment.storeOp = sym_to_store_op(store_op) 57 | attachment.stencilLoadOp = sym_to_load_op(stencil_load_op) 58 | attachment.stencilStoreOp = sym_to_store_op(stencil_store_op) 59 | attachment.initialLayout = sym_to_image_layout(initial_layout) 60 | attachment.finalLayout = sym_to_image_layout(final_layout) 61 | @attachments << attachment 62 | @attachments.size - 1 63 | end 64 | 65 | def add_subpass(**args) 66 | subpass = Vulkan::RenderPass::Subpass.new(@vk, **args) 67 | @subpasses << subpass 68 | subpass 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/vulkan/render_pass/subpass.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class RenderPass 3 | class Subpass 4 | include Vulkan::Conversions 5 | 6 | def initialize(vk, bind_point: :graphics) 7 | @color_attachments = [] 8 | @input_attachments = [] 9 | @resolve_attachments = [] 10 | @preserve_attachments = [] 11 | 12 | @ptr = VkSubpassDescription.malloc 13 | @ptr.pipelineBindPoint = sym_to_pipeline_bind_point(bind_point) 14 | @ptr.colorAttachmentCount = 0 15 | @ptr.pColorAttachments = nil 16 | @ptr.inputAttachmentCount = 0 17 | @ptr.pInputAttachments = nil 18 | @ptr.pResolveAttachments = nil 19 | @ptr.pDepthStencilAttachment = nil 20 | @ptr.preserveAttachmentCount = 0 21 | @ptr.pPreserveAttachments = nil 22 | end 23 | 24 | def add_attachment_ref(ary, index:, layout:, addr_mbr:, count_mbr:) 25 | ary << create_attachment_ref(index, layout) 26 | update_attachment_refs(ary, addr_mbr: addr_mbr, count_mbr: count_mbr) 27 | end 28 | 29 | def update_attachment_refs(ary, addr_mbr:, count_mbr:) 30 | @ptr.send(:"#{addr_mbr}=", array_of_structures(ary)) 31 | @ptr.send(:"#{count_mbr}=", ary.size) if count_mbr 32 | end 33 | 34 | def add_color_attachment_ref(index:, layout: :color) 35 | add_attachment_ref(@color_attachments, index: index, 36 | layout: layout, 37 | addr_mbr: :pColorAttachments, 38 | count_mbr: :colorAttachmentCount) 39 | end 40 | 41 | def add_resolve_attachment_ref(index:, layout: :color) 42 | add_attachment_ref(@resolve_attachments, index: index, 43 | layout: layout, 44 | addr_mbr: :pResolveAttachments, 45 | count_mbr: nil) 46 | end 47 | 48 | def add_input_attachment_ref(index:, layout:) 49 | add_attachment_ref(@input_attachments, index: index, 50 | layout: layout, 51 | addr_mbr: :pInputAttachments, 52 | count_mbr: :inputAttachmentCount) 53 | end 54 | 55 | def add_preserve_attachment_ref(index:, layout:) 56 | add_attachment_ref(@preserve_attachments, index: index, 57 | layout: layout, 58 | addr_mbr: :pPreserveStencilAttachments, 59 | count_mbr: :preserveStencilAttachmentCount) 60 | end 61 | 62 | def set_depth_stencil_attachment_ref(index:, layout: :depth_stencil_attachment_optimal) 63 | ref = create_attachment_ref(index, layout) 64 | @depth_stencil_attachment = ref 65 | @ptr.pDepthStencilAttachment = ref 66 | end 67 | 68 | def to_ptr 69 | @ptr&.to_ptr 70 | end 71 | 72 | def create_attachment_ref(index, layout) 73 | ref = VkAttachmentReference.malloc 74 | ref.attachment = index # attachment index 75 | ref.layout = sym_to_image_layout(layout) 76 | ref 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/vulkan/sampler.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class Sampler 3 | include Vulkan::Checks 4 | include Vulkan::Conversions 5 | include Vulkan::Finalizer 6 | 7 | def initialize(vk, device, 8 | mag_filter: :linear, 9 | min_filter: :linear, 10 | address_mode_u: :repeat, 11 | address_mode_v: :repeat, 12 | address_mode_w: :repeat, 13 | anisotropy: device.feature_enabled?(:sampler_anisotropy), 14 | max_anisotropy: anisotropy ? 16 : 1, 15 | border_color: :int_opaque_black, 16 | unnormalized_coords: false, 17 | compare: false, 18 | compare_op: :always, 19 | mipmap_mode: :linear, 20 | mip_lod_bias: 0, 21 | min_lod: 0, 22 | max_lod: 0 23 | ) 24 | sampler_info = VkSamplerCreateInfo.malloc 25 | sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO 26 | sampler_info.magFilter = sym_to_filter(mag_filter) 27 | sampler_info.minFilter = sym_to_filter(min_filter) 28 | sampler_info.addressModeU = sym_to_sampler_address_mode(address_mode_u) 29 | sampler_info.addressModeV = sym_to_sampler_address_mode(address_mode_v) 30 | sampler_info.addressModeW = sym_to_sampler_address_mode(address_mode_w) 31 | sampler_info.anisotropyEnable = bool_to_vk(anisotropy) 32 | sampler_info.maxAnisotropy = max_anisotropy 33 | sampler_info.borderColor = sym_to_border_color(border_color) 34 | sampler_info.unnormalizedCoordinates = bool_to_vk(unnormalized_coords) 35 | sampler_info.compareEnable = bool_to_vk(compare) 36 | sampler_info.compareOp = sym_to_compare_op(compare_op) 37 | sampler_info.mipmapMode = sym_to_sampler_mipmap_mode(mipmap_mode) 38 | sampler_info.mipLodBias = mip_lod_bias 39 | sampler_info.minLod = min_lod 40 | sampler_info.maxLod = max_lod 41 | 42 | handle_p = Vulkan.create_value('void *', nil) 43 | check_result vk.vkCreateSampler(vk.device, sampler_info, nil, handle_p) 44 | @handle = handle_p.value 45 | finalize_with vk, :vkDestroySampler, vk.device, @handle, nil 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/vulkan/semaphore.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class Semaphore 3 | include Vulkan::Checks 4 | include Vulkan::Finalizer 5 | 6 | def initialize(vk) 7 | @vk = vk 8 | semaphore_info = VkSemaphoreCreateInfo.malloc 9 | semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO 10 | sem_p = Vulkan.create_value('void *', nil) 11 | check_result @vk.vkCreateSemaphore(@vk.device, semaphore_info, nil, sem_p) 12 | @handle = sem_p.value 13 | finalize_with @vk, :vkDestroySemaphore, @vk.device, @handle, nil 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/vulkan/shader_stage.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class ShaderStage 3 | include Vulkan::Checks 4 | include Vulkan::Conversions 5 | include Vulkan::Finalizer 6 | 7 | attr_reader :entry_point, :stage, :module_handle 8 | 9 | def initialize(vk, code: nil, 10 | file_path: nil, 11 | entry_point: 'main', 12 | stage:) 13 | code ||= File.open(file_path, 'rb') { |file| file.read } 14 | @vk = vk 15 | @entry_point = entry_point 16 | @stage = stage 17 | 18 | code_p = Fiddle::Pointer.malloc(code.size + 1) 19 | code_p[0, code.size] = code 20 | create_info = VkShaderModuleCreateInfo.malloc 21 | create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO 22 | create_info.codeSize = code.size 23 | create_info.pCode = code_p 24 | 25 | module_handle_p = Vulkan.create_value('void *', nil) 26 | check_result @vk.vkCreateShaderModule(vk.device, create_info, nil, module_handle_p) 27 | @module_handle = module_handle_p.value 28 | finalize_with vk, :vkDestroyShaderModule, vk.device, @module_handle, nil 29 | 30 | @handle = @module_handle 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/vulkan/surface.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class Surface 3 | include Vulkan::Finalizer 4 | 5 | def initialize(instance, handle) 6 | @instance = instance 7 | @handle = handle 8 | finalize_with Vulkan[instance, nil], :vkDestroySurfaceKHR, instance.to_ptr, @handle, nil 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/vulkan/swapchain.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class Swapchain 3 | include Vulkan::Checks 4 | include Vulkan::Conversions 5 | include Vulkan::Finalizer 6 | 7 | attr_reader :extent 8 | attr_reader :color_space 9 | attr_reader :format 10 | attr_reader :image_views 11 | 12 | def initialize(instance, device, surface:, 13 | surface_width:, 14 | surface_height:, 15 | app_config: {}, 16 | builder_class: Vulkan::SwapchainBuilder, 17 | image_usage: VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, 18 | image_array_layers: 1, 19 | sharing_mode: VK_SHARING_MODE_EXCLUSIVE, 20 | composite_alpha: VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, 21 | clipped: true, 22 | old_swapchain: nil) 23 | @vk = Vulkan[instance, device] 24 | 25 | info = device.physical_device.swapchain_surface_info(surface) 26 | builder = builder_class.new(info, app_config) 27 | @extent = { width: builder.width(surface_width), height: builder.height(surface_height) } 28 | 29 | image_format = builder.format 30 | @format = image_format[:format] 31 | @color_space = image_format[:color_space] 32 | 33 | create_info = VkSwapchainCreateInfoKHR.malloc 34 | create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR 35 | create_info.surface = surface.to_ptr 36 | create_info.minImageCount = builder.image_count 37 | create_info.imageFormat = @format 38 | create_info.imageColorSpace = @color_space 39 | create_info.imageExtent.width = @extent[:width] 40 | create_info.imageExtent.height = @extent[:height] 41 | create_info.imageArrayLayers = image_array_layers 42 | create_info.imageUsage = image_usage 43 | create_info.imageSharingMode = sharing_mode 44 | create_info.queueFamilyIndexCount = 0 45 | create_info.pQueueFamilyIndices = nil 46 | create_info.preTransform = syms_to_surface_transforms(builder.transformation) 47 | create_info.compositeAlpha = composite_alpha 48 | create_info.presentMode = sym_to_present_mode(builder.presentation_mode) 49 | create_info.clipped = clipped ? VK_TRUE : VK_FALSE 50 | create_info.oldSwapchain = old_swapchain 51 | 52 | swapchain = Vulkan.create_value("void *", nil) 53 | check_result @vk.vkCreateSwapchainKHR(device.to_ptr, create_info, nil, swapchain) 54 | @handle = swapchain.value 55 | finalize_with @vk, :vkDestroySwapchainKHR, device.to_ptr, @handle, nil 56 | 57 | image_count_p = Vulkan.create_value('uint32_t', 0) 58 | check_result @vk.vkGetSwapchainImagesKHR(device.to_ptr, @handle, image_count_p, nil) 59 | images_p = Vulkan.struct("images[#{image_count_p.value}]" => 'void *handle').malloc 60 | check_result @vk.vkGetSwapchainImagesKHR(device.to_ptr, @handle, image_count_p, images_p); 61 | @image_views = images_p.images.map { |i| ImageView.new(@vk, i.handle, sym_to_image_format(@format)) } 62 | 63 | @image_index_p = Vulkan.create_value('uint32_t', 0) 64 | end 65 | 66 | def [](method_name) 67 | send method_name 68 | end 69 | 70 | def width 71 | @extent[:width] 72 | end 73 | 74 | def height 75 | @extent[:height] 76 | end 77 | 78 | def size 79 | image_views.size 80 | end 81 | 82 | def next_image_index(semaphore: nil, fence: nil, timeout: 0xffffffffffffffff) 83 | @vk.vkAcquireNextImageKHR(@vk.device, to_ptr, timeout, semaphore, fence, @image_index_p) 84 | @image_index_p.value 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/vulkan/swapchain_builder.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class SwapchainBuilder 3 | attr_reader :surface_info 4 | attr_reader :config 5 | 6 | def initialize(swapchain_surface_info, config = {}) 7 | @surface_info = swapchain_surface_info 8 | @config = config 9 | raise 'swapchain not supported' unless usage_supported? 10 | end 11 | 12 | # Chooses and returns a swapchain image format from those supported. 13 | # An override may be specified in 14 | # `config[:graphics][:swapchain][:image][:format]` and/or 15 | # `config[:graphics][:swapchain][:image][:color_space]`. The 16 | # defaults are `:r8g8b8a8_unorm` and `:srgb_nonlinear`, respectively. 17 | def format 18 | supported = surface_info.formats 19 | 20 | # By default use the most widely supported R8 G8 B8 A8 format with 21 | # nonlinear colorspace. 22 | preferred_format = { format: :r8g8b8a8_unorm, color_space: :srgb_nonlinear } 23 | 24 | # If app config specifies an override, and it is supported, then 25 | # use it. 26 | if override = config.dig(:graphics, :swapchain, :image) 27 | preferred_format[:format] = override[:format] if override.key?(:format) 28 | preferred_format[:color_space] = override[:color_space] if override.key?(:color_space) 29 | end 30 | 31 | # If the list contains only one entry with an undefined format then 32 | # it means that there are no preferred surface formats and any can 33 | # be chosen. 34 | return preferred_format if supported.size == 1 && supported[0][:format] == :undefined 35 | 36 | # Check if the preferred format is supported, and use it if possible 37 | return preferred_format if supported.include?(preferred_format) 38 | 39 | # No match found, try to match the format ignoring colorspace 40 | supported.each { |sup| return sup if sup[:format] == preferred_format[:format] } 41 | 42 | # No match, return the first format in the list 43 | return supported.first || raise('no supported swapchain image formats') 44 | end 45 | 46 | def presentation_mode 47 | # :fifo should always be available; 48 | # :mailbox is the lowest latency V-Sync enabled mode (something like 49 | # triple-buffering) so use it if available 50 | supported = surface_info.presentation_modes 51 | preferred_mode = config.dig(:graphics, :swapchain, :vsync) == false ? :immediate : 52 | config.dig(:graphics, :swapchain, :presentation_mode)&.to_sym 53 | if preferred_mode && supported.include?(preferred_mode) 54 | return preferred_mode 55 | else 56 | return :mailbox if supported.include?(:mailbox) 57 | return :fifo if supported.include?(:fifo) 58 | return :fifo_relaxed if supported.include?(:fifo_relaxed) 59 | return :immediate if supported.include?(:immediate) 60 | return raise('no supported presentation modes (available: %s)' % supported.inspect) 61 | end 62 | end 63 | 64 | def transformation 65 | return :identity if @surface_info.capabilities[:supported_transforms].include?(:identity) 66 | return @surface_info.capabilities[:current_transform] 67 | end 68 | 69 | def usage_supported? 70 | usage = @surface_info.capabilities[:supported_usage] 71 | usage.include?(:transfer_dst) 72 | end 73 | 74 | def width(desired_width) 75 | surface_capabilities = surface_info.capabilities 76 | desired_width ||= surface_capabilities[:current_extent][:width] 77 | if desired_width == -1 78 | desired_width = (config.dig(:graphics, :swapchain, :width)) || 79 | (config.dig(:window, :width)) || 80 | 640 81 | end 82 | desired_width.clamp(surface_capabilities[:min_image_extent][:width], 83 | surface_capabilities[:max_image_extent][:width]) 84 | end 85 | 86 | def height(desired_height) 87 | surface_capabilities = surface_info.capabilities 88 | desired_height ||= surface_capabilities[:current_extent][:height] 89 | if desired_height == -1 90 | desired_height = (config.dig(:graphics, :swapchain, :height)) || 91 | (config.dig(:window, :height)) || 92 | 640 93 | end 94 | desired_height.clamp(surface_capabilities[:min_image_extent][:height], 95 | surface_capabilities[:max_image_extent][:height]) 96 | end 97 | 98 | def dimensions(desired_width = nil, desired_height = nil) 99 | [width(desired_width), height(desired_height)] 100 | end 101 | 102 | def image_count 103 | count = @surface_info.capabilities[:min_image_count] + 1 104 | if @surface_info.capabilities[:max_image_count] > 0 && 105 | count > @surface_info.capabilities[:max_image_count] 106 | count = @surface_info.capabilities[:max_image_count] 107 | end 108 | return count 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/vulkan/swapchain_surface_info.rb: -------------------------------------------------------------------------------- 1 | module Vulkan 2 | class SwapchainSurfaceInfo < Hash 3 | include Vulkan::Checks 4 | include Vulkan::Conversions 5 | 6 | attr_accessor :builder 7 | 8 | def initialize(instance, physical_device, surface) 9 | super() 10 | @vk = Vulkan[instance, nil] 11 | @physical_device = physical_device 12 | @surface = surface 13 | merge! capabilities: capabilities, 14 | formats: formats, 15 | presentation_modes: presentation_modes 16 | end 17 | 18 | def presentation_modes 19 | @presentation_modes ||= begin 20 | modes_count_p = Vulkan.create_value('uint32_t', 0) 21 | check_result @vk.vkGetPhysicalDeviceSurfacePresentModesKHR(@physical_device.to_ptr, @surface.to_ptr, modes_count_p, nil) 22 | modes_p = Vulkan.struct(["VkPresentModeKHR modes[#{modes_count_p.value}]"]).malloc 23 | check_result @vk.vkGetPhysicalDeviceSurfacePresentModesKHR(@physical_device.to_ptr, @surface.to_ptr, modes_count_p, modes_p) 24 | modes_p.modes.map { |mode| present_mode_to_sym(mode) } 25 | end 26 | end 27 | 28 | def formats 29 | @formats ||= begin 30 | format_count_p = Vulkan.create_value('uint32_t', 0) 31 | check_result @vk.vkGetPhysicalDeviceSurfaceFormatsKHR(@physical_device.to_ptr, @surface.to_ptr, format_count_p, nil) 32 | formats_p = Vulkan.struct("formats[#{format_count_p.value}]" => VkSurfaceFormatKHR).malloc 33 | check_result @vk.vkGetPhysicalDeviceSurfaceFormatsKHR(@physical_device.to_ptr, @surface.to_ptr, format_count_p, formats_p) 34 | formats_p.formats.map { |fmt| struct_to_hash(fmt) } 35 | end 36 | end 37 | 38 | def capabilities 39 | @capabilities ||= begin 40 | capabilities = VkSurfaceCapabilitiesKHR.malloc 41 | check_result @vk.vkGetPhysicalDeviceSurfaceCapabilitiesKHR(@physical_device.to_ptr, @surface.to_ptr, capabilities) 42 | capabilities = struct_to_hash(capabilities) 43 | capabilities[:supported_transforms_flags] = capabilities[:supported_transforms] 44 | capabilities[:supported_transforms] = flags_to_symbols(capabilities[:supported_transforms_flags], /^VK_SURFACE_TRANSFORM_(.*?)_BIT/) 45 | capabilities[:current_transform_flags] = capabilities[:current_transform] 46 | capabilities[:current_transform] = flags_to_symbols(capabilities[:current_transform_flags], /^VK_SURFACE_TRANSFORM_(.*?)_BIT/) 47 | capabilities[:supported_composite_alpha_flags] = capabilities[:supported_composite_alpha] 48 | capabilities[:supported_composite_alpha] = flags_to_symbols(capabilities[:supported_composite_alpha_flags], /^VK_COMPOSITE_ALPHA_(.*?)_BIT/) 49 | capabilities[:supported_usage] = flags_to_symbols(capabilities[:supported_usage_flags], /^VK_IMAGE_USAGE_(.*?)_BIT/) 50 | capabilities 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/vulkan/version.rb: -------------------------------------------------------------------------------- 1 | require 'vulkan/generated/version' 2 | 3 | module Vulkan 4 | VULKAN_RUBY_VERSION = [ 5 ] 5 | 6 | VERSION = [ VK_API_VERSION_MAJOR, 7 | VK_API_VERSION_MINOR, 8 | VK_API_VERSION_PATCH, 9 | *VULKAN_RUBY_VERSION ].join('.') 10 | end 11 | -------------------------------------------------------------------------------- /lib/vulkan/window_surface.rb: -------------------------------------------------------------------------------- 1 | require 'vulkan/surface' 2 | 3 | module Vulkan 4 | # Provides a handle to an underlying window surface based on an SDL2 5 | # window (or otherwise, any object which responds to `vk_create_surface` 6 | # and which returns the integer address of the created surface), and a 7 | # Vulkan instance. 8 | class WindowSurface < Surface 9 | def initialize(instance, window) 10 | super(instance, window.vk_create_surface(instance.to_ptr.to_i)) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /tasks/examples.rake: -------------------------------------------------------------------------------- 1 | examples = [] 2 | 3 | namespace :examples do 4 | Dir[File.expand_path('../examples/*.rb', __dir__)].each do |filename| 5 | basename = File.basename(filename, '.rb') 6 | examples << "examples:#{basename}" 7 | desc "run the '#{basename}' example script. Influential vars: DEBUG=1, CALL_TRACE=1, MAX_FRAMES=N" 8 | task basename do 9 | ENV['COVERAGE'] = basename 10 | sh 'bundle', 'exec', 'ruby', filename 11 | end 12 | 13 | if basename[/^(.*?)_/] 14 | # desc "alias for #{basename}" 15 | task $1 => "examples:#{basename}" 16 | end 17 | end 18 | end 19 | 20 | desc 'Run all examples. Influential vars: DEBUG=1, CALL_TRACE=1, MAX_FRAMES=N' 21 | task examples: examples 22 | 23 | task :default do 24 | # also run all examples as part of the tests 25 | ENV['MAX_FRAMES'] = '5' 26 | ENV['DEBUG'] = '1' 27 | Rake::Task[:examples].invoke 28 | puts 'Execution successful.' 29 | end 30 | -------------------------------------------------------------------------------- /tasks/fetch.rake: -------------------------------------------------------------------------------- 1 | desc 'fetch latest version of vk.xml' 2 | task :fetch do 3 | require 'open-uri' 4 | require 'openssl' 5 | require 'vulkan/version' 6 | open(vk_xml_path, 'wb') do |file| 7 | file << URI.open("https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/main/xml/vk.xml", 8 | :ssl_verify_mode => OpenSSL::SSL::VERIFY_NONE).read 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /tasks/generate.rake: -------------------------------------------------------------------------------- 1 | desc 'regenerate everything' 2 | task :generate => %w( generate:types 3 | generate:enums 4 | generate:structs 5 | generate:extensions 6 | generate:commands 7 | generate:version 8 | ) 9 | -------------------------------------------------------------------------------- /tasks/generate/commands.rake: -------------------------------------------------------------------------------- 1 | namespace :generate do 2 | desc 'generate commands' 3 | task :commands do 4 | core_features = vk_xml.css("feature[name=VK_VERSION_1_0] command").map { |command| command.attributes['name'].to_s } 5 | addl_features = vk_xml.css("feature command").map { |command| command.attributes['name'].to_s } - core_features 6 | instance_functions = vk_xml.css("require[comment='Device initialization'] command").map { |command| command.attributes['name'].to_s } 7 | 8 | open(generate_dir.join('commands.rb'), 'w') do |f| 9 | f.puts header_comment 10 | f.puts 11 | f.puts 'module Vulkan' 12 | aliases = [] 13 | max_name_len = vk_xml.css("commands command proto").map { |name| name.text.size }.max 14 | vk_xml.xpath('//commands/command').each do |command| 15 | if command.attributes['name'] && command.attributes['alias'] 16 | name = command.attributes['name'].to_s 17 | command_alias = command.attributes['alias'].to_s 18 | aliases << [name, command_alias] 19 | # f.puts " def #{name}(*args); #{command_alias}(*args); end" 20 | else 21 | name = command.css('proto name').text 22 | next if name == 'vkGetInstanceProcAddr' 23 | proto = command.xpath('proto').text 24 | params = command.xpath('param').map(&:text).join(', ') 25 | if proto.to_s.strip.size == 0 || params.to_s.strip.size == 0 26 | puts "WARN: unhandled command: #{proto}(#{params}) (#{command.to_s})" 27 | else 28 | # if instance_functions.include?(name) || params[/^vkInstance\s/] 29 | # f.puts " lookup_instance_function #{proto.inspect.ljust(max_name_len+2)}, #{params.inspect}" 30 | # else 31 | # f.puts " lookup_device_function #{proto.inspect.ljust(max_name_len+2)}, #{params.inspect}" 32 | # end 33 | f.puts " register_function #{proto.inspect.ljust(max_name_len+2)}, #{params.inspect}" 34 | end 35 | end 36 | # 37 | # VkResult vkCreateInstance 38 | # const VkInstanceCreateInfo* pCreateInfo 39 | # const VkAllocationCallbacks* pAllocator 40 | # VkInstance* pInstance 41 | # 42 | end 43 | # if aliases.any? 44 | # f.puts " class << self" 45 | # aliases.each { |a| f.puts " alias #{a[0].ljust(max_name_len)} #{a[1]}"} 46 | # f.puts " end" 47 | # end 48 | f.puts 'end' 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /tasks/generate/enums.rake: -------------------------------------------------------------------------------- 1 | ALL_ENUMS = {} 2 | 3 | def enum_offset(extnum, offset, dir = 1) 4 | base_value = 1000000000 5 | range_size = 1000 6 | (base_value + (extnum - 1) * range_size + offset) * dir 7 | end 8 | 9 | def find_enum_value(name) 10 | ALL_ENUMS.each do |category, enums| 11 | return enums[name] if enums.keys.include?(name) 12 | end 13 | nil 14 | end 15 | 16 | def resolve_enum_alias(name) 17 | val = find_enum_value(name) 18 | if val.kind_of?(Hash) && val[:alias] 19 | return resolve_enum_alias(val[:alias]) 20 | else 21 | return val 22 | end 23 | end 24 | 25 | def process_literal_value(value) 26 | value.sub(/(\d+)[fFuUlL]+/, '\1') 27 | end 28 | 29 | namespace :generate do 30 | desc 'generate enums' 31 | task :enums do 32 | vk_xml.xpath('/registry/enums').each do |enums| 33 | raise "duplicate enums? #{enums}" if ALL_ENUMS[enums['name']] 34 | _enums = ALL_ENUMS[enums['name']] = {} 35 | enums.xpath('enum').each do |enum| 36 | raise "duplicate enum? #{enum}" if _enums[enum['name']] 37 | value = nil 38 | if enum['value'] then value = process_literal_value(enum['value']) 39 | elsif enum['alias'] then value = { alias: enum['alias'] } 40 | elsif enum['bitpos'] then value = 1 << enum['bitpos'].to_i 41 | end 42 | raise "could not compute enum value: #{enum}" unless value 43 | _enums[enum['name']] = value 44 | end 45 | end 46 | 47 | process_enum = proc do |enum, extnum = nil| 48 | extnumber = enum['extnumber'] || extnum 49 | if enum['extends'] && enum['offset'] 50 | category = ALL_ENUMS[enum['extends']] || raise("Enum category not found for #{enum}") 51 | raise "No extnumber" unless extnumber 52 | value = enum_offset(extnumber.to_i, enum['offset'].to_i, enum['dir'] == '-' ? -1 : 1) 53 | raise "Could not compute offset for #{enum}" unless value 54 | elsif enum['extends'] && enum['bitpos'] 55 | category = ALL_ENUMS[enum['extends']] || raise("Enum category not found for #{enum}") 56 | value = 1 << enum['bitpos'].to_i 57 | elsif enum['alias'] 58 | category = ALL_ENUMS[enum['extends']] || ALL_ENUMS[nil] ||= {} 59 | value = { alias: enum['alias'] } 60 | elsif enum['extends'] && enum['value'] 61 | category = ALL_ENUMS[enum['extends']] || raise("Enum category not found for #{enum}") 62 | value = process_literal_value(enum['value']) 63 | elsif enum['value'] 64 | category = ALL_ENUMS[nil] ||= {} 65 | value = process_literal_value(enum['value']) 66 | else 67 | if find_enum_value(enum['name']) 68 | next # we already have a value for this so carry on 69 | else 70 | raise "Unhandled enum: #{enum}" 71 | end 72 | end 73 | 74 | raise "no category for #{enum}" unless category 75 | raise "no value for #{enum}" unless value 76 | existing_value = category[enum['name']] 77 | raise "duplicate enum? #{enum} (current: #{existing_value})" if existing_value && existing_value != value 78 | category[enum['name']] = value 79 | end 80 | 81 | vk_xml.xpath('/registry/feature/require/enum').each do |enum| 82 | process_enum.call(enum) 83 | end 84 | 85 | vk_xml.xpath('/registry/extensions/extension').each do |extension| 86 | extension.xpath('require/enum').each do |enum| 87 | process_enum.call(enum, extension['number']) 88 | end 89 | end 90 | 91 | open(generate_dir.join('enums.rb'), 'w') do |f| 92 | f.puts header_comment 93 | f.puts 94 | f.puts 'module Vulkan' 95 | 96 | ALL_ENUMS.each do |category, enums| 97 | f.puts " # #{category}" unless category.nil? 98 | 99 | enums.each do |name, value| 100 | if value.kind_of?(Hash) && value[:alias] 101 | f.puts " #{name} = #{resolve_enum_alias(value[:alias])} # Alias of #{value[:alias]}" 102 | else 103 | f.puts " #{name} = #{value}" 104 | end 105 | end 106 | f.puts 107 | end 108 | 109 | f.puts 'end' 110 | end 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /tasks/generate/extensions.rake: -------------------------------------------------------------------------------- 1 | namespace :generate do 2 | desc 'generate extension enums, types, etc' 3 | task :extensions do 4 | # rm_rf generate_dir.join('extensions') 5 | # mkdir_p generate_dir.join('extensions') 6 | # File.open(generate_dir.join('extensions.rb'), 'w') do |extout| 7 | # extout.puts header_comment 8 | # extout.puts 9 | 10 | # vk_xml.css('extension').each do |extension| 11 | # extout.puts "require 'vulkan/generated/extensions/#{extension['name'].downcase}'" 12 | # extnum = extension['number'].to_i 13 | # File.open(generate_dir.join('extensions/%s.rb' % extension['name'].downcase), 'w') do |f| 14 | # f.puts header_comment 15 | # f.puts 16 | # f.puts 'module Vulkan' 17 | 18 | # extension.css('enum').each do |enum| 19 | # dir = enum['dir'] == '-' ? -1 : enum['dir'] 20 | # alias_name = enum['alias'] 21 | # value = enum['value'] || enum_offset(enum['extnumber'] || extnum, enum['offset'].to_i, (dir || 1).to_i) 22 | # if alias_name 23 | # f.puts " #{enum['name']} = #{alias_name} unless defined?(#{enum['name']})" 24 | # else 25 | # f.puts " #{enum['name']} = #{value} unless defined?(#{enum['name']})" 26 | # end 27 | # end 28 | 29 | # f.puts 'end' 30 | # end 31 | # end 32 | # end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /tasks/generate/structs.rake: -------------------------------------------------------------------------------- 1 | namespace :generate do 2 | desc 'generate structs' 3 | task structs: ['generate:types', 'generate:enums'] do 4 | require 'fiddle' 5 | open(generate_dir.join('structs.rb'), 'w') do |f| 6 | f.puts header_comment 7 | f.puts 8 | f.puts 'module Vulkan' 9 | @structs = {} 10 | if Fiddle::WINDOWS 11 | @structs['SECURITY_ATTRIBUTES'] = [ 12 | 'struct', 13 | nil, 14 | 'DWORD nLength', 15 | 'LPVOID lpSecurityDescriptor', 16 | 'BOOL bInheritHandle' 17 | ] 18 | end 19 | 2.times do 20 | @structs.tap do |processed| 21 | vk_xml.xpath('//types/type').each do |struct| 22 | if struct.attributes['category']&.value == 'struct' || struct.attributes['category']&.value == 'union' 23 | name = struct.attributes['name']&.value 24 | raise "struct has no name: #{struct.to_s.inspect}" unless name 25 | if alias_name = struct.attributes['alias']&.value 26 | raise "struct alias has not been processed yet: #{struct.to_s.inspect}" unless processed[alias_name] 27 | # f.puts " #{name} = #{alias_name}" 28 | processed[name] = alias_name 29 | else 30 | # MyStruct = struct ['int i', 'char c', { position: ['float x', 'float y'] }] 31 | members = struct.xpath('member').map do |m| 32 | m.xpath('comment').remove 33 | type = m.xpath('type').text.to_s.strip 34 | is_pointer = m.to_s[/<\/type>\s*\*/] 35 | # type = 'void' if is_pointer 36 | array = m.text[/\[[0-9]+\]$/] 37 | if processed[type] && !is_pointer 38 | arylen = array || '' 39 | arylen = "[#{m.xpath('enum').text.to_s.strip}]" if m.xpath('enum').any? 40 | nested_name = m.xpath('name').text.to_s.strip + arylen 41 | { nested_name => type } 42 | else 43 | # comment = m.xpath('comment').text.to_s.strip 44 | m.xpath('enum').each do |enum| 45 | enum.content = "\#{" + enum.text + "}" unless enum.text['#{'] 46 | end 47 | # m.text.to_s.strip 48 | arylen = '' 49 | arylen = "[#{m.xpath('enum').text.to_s.strip}]" if m.xpath('enum').any? 50 | [type, is_pointer ? '*' : '', m.xpath('name').text.to_s.strip + arylen, array] 51 | end 52 | end 53 | # processed[name] = name 54 | comment = struct.attributes['comment']&.value 55 | processed[name] = [ struct.attributes['category'].value, comment, *members ] 56 | end 57 | end 58 | end 59 | end 60 | end 61 | 62 | # flattens hashes and arrays alike into a single array of values 63 | totally_flatten = proc do |mbr| 64 | result = [] 65 | if mbr.kind_of?(Array) || mbr.kind_of?(Hash) 66 | mbr.flatten.each do |elem| 67 | result.concat totally_flatten.call elem 68 | end 69 | else 70 | result << mbr 71 | end 72 | result 73 | end 74 | 75 | # sort alphabetically, then loop through each pending item, tracking 76 | # down dependencies and dumping them before dumping the item first. 77 | pending = Hash[@structs.dup.sort { |a, b| a[0] <=> b[0] }] 78 | dump = proc do |item| 79 | name, members_or_alias = *item 80 | pending.delete name 81 | # find dependencies, dump them first 82 | totally_flatten.call([members_or_alias]).each do |member| 83 | if pending.key?(member) 84 | dump.call [member, pending[member]] 85 | end 86 | end 87 | # dump item 88 | comment = nil 89 | struct_or_union = members_or_alias 90 | str = case members_or_alias 91 | when String then members_or_alias # alias 92 | else 93 | struct_or_union = members_or_alias.shift 94 | comment = members_or_alias.shift 95 | members = members_or_alias.map do |member| 96 | if member.kind_of?(Hash) 97 | member = member.to_a[0] 98 | "{ \"#{member[0]}\" => #{member[1]} }" 99 | else 100 | member.compact.join(' ').inspect.gsub(/\\\#/, '#') 101 | end 102 | end 103 | "#{struct_or_union} [#{members.join(",\n")}]" 104 | end 105 | f.puts " # #{comment.sub(/^\s*\/\/\s*/, '')}" if comment 106 | str = " #{name} = #{str}" 107 | f.print str.lines.first 108 | f.puts str.lines[1..-1].map { |line| (" " * (name.size + struct_or_union.size + 7)) + line }.join 109 | end 110 | while pending.any? 111 | dump.call pending.to_a.first 112 | f.puts 113 | end 114 | f.puts 'end' 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /tasks/generate/types.rake: -------------------------------------------------------------------------------- 1 | # Include the manually specified types here, and then we will skip them while 2 | # processing vk.xml if they are encountered. This way, all special cases need 3 | # only be added to that file to be properly dealt with everywhere. 4 | require 'vulkan/manual_types' 5 | ::ManualTypes = Class.new do 6 | extend Fiddle::Importer 7 | # HACK, fiddle only initializes this if we call dlload which we don't need here 8 | @type_alias = {} 9 | include Vulkan::ManualTypes 10 | end.new 11 | 12 | TYPE_ALIASES = {} 13 | 14 | # Returns an array whose entries are 2-element arrays containing first the 15 | # name of the type to be created, and second the name of the type that it is 16 | # an alias of. The return value is sorted such that types that depend on 17 | # other types appear later. 18 | def resolve_type_aliases(aliases = TYPE_ALIASES.dup) 19 | result = [] 20 | aliases.dup.each do |name, alias_of| 21 | next if aliases[alias_of] # we want only leaf nodes 22 | aliases.delete name 23 | result << [name, alias_of] 24 | end 25 | result.concat resolve_type_aliases(aliases) unless aliases.empty? 26 | result 27 | end 28 | 29 | namespace :generate do 30 | desc 'generate types' 31 | task :types do 32 | open(generate_dir.join('types.rb'), 'w') do |f| 33 | f.puts header_comment 34 | f.puts 35 | f.puts 'module Vulkan' 36 | 37 | vk_xml.xpath('/registry/types/type').each do |type| 38 | raise "duplicate type? #{type}" if TYPE_ALIASES[type['name']] 39 | value = nil 40 | # skip types we can't make use of 41 | next if type['category'] == 'include' 42 | 43 | name = type['name'] || type.xpath('name')&.text.to_s 44 | name = nil if name.size == 0 45 | if name 46 | # skip C types defined by Fiddle (see lib/fiddle/cparser.rb #parse_ctype) 47 | next if %w( char void int double float size_t ssize_t short ).include?(name) 48 | # skip types we manually defined 49 | next if ::ManualTypes.class.send(:type_alias).key?(name) 50 | end 51 | 52 | # types that depend on an external dependency, which we don't have access 53 | # to. Try to define a default for them, which may or may not be correct. 54 | # Note they won't be defined if they are already typealias'ed everywhere. 55 | # We do this for a few known types in lib/vulkan/generated.rb. 56 | # FIXME is there a better solution? Do nothing? Always manually define them? 57 | if type['requires'] && type['requires'][/\.h/] 58 | raise "No type name for #{type}" unless name 59 | f.puts " typealias '#{name}', 'void *' unless send(:type_alias).key?('#{name}') # defined in #{type['requires']}" 60 | elsif type['category'] == 'define' 61 | raise "No type name for #{type}" unless name 62 | next if name['VERSION'] 63 | f.puts " #{name} = 0 # dummy value, its real value could not be converted" 64 | elsif type['category'] == 'basetype' 65 | _type = type.xpath('type')&.text 66 | raise "No type name for #{type}" unless name 67 | if _type.size == 0 68 | # we are about to choose type 'void' in hopes that it will only ever 69 | # be used as a pointer (void *). In case we are wrong, check whether 70 | # it was manually specified in lib/vulkan/generated.rb first. Then 71 | # we can address these on a case by case basis. 72 | f.puts " typealias '#{name}', 'void' unless send(:type_alias).key?('#{name}')" 73 | else 74 | f.puts " typealias '#{name}', '#{_type}'" 75 | end 76 | elsif type['category'] == 'bitmask' 77 | _type = type.xpath('type')&.text 78 | _type = nil if _type&.size == 0 79 | raise "No type name for #{type}" unless name.size > 0 80 | if _type 81 | f.puts " typealias '#{name}', '#{_type}'" 82 | elsif type['alias'] 83 | TYPE_ALIASES[name] = type['alias'] 84 | end 85 | elsif type['category'] == 'handle' 86 | # all handles are pointers 87 | raise "No type name for #{type}" unless name.size > 0 88 | f.puts " typealias '#{name}', 'void *' # handle" 89 | elsif type['category'] == 'enum' 90 | # FIXME we shouldn't assume an enum is an int, it could be smaller 91 | # depending on the compiler. 92 | raise "No type name for #{type}" unless name.size > 0 93 | f.puts " typealias '#{name}', 'int' # enum" 94 | elsif type['category'] == 'funcpointer' 95 | # function pointers are pointers. 96 | # TODO do something with the arguments? Not sure fiddle knows/cares. 97 | raise "No type name for #{type}" unless name.size > 0 98 | f.puts " typealias '#{name}', 'void *' # function pointer" 99 | elsif type['category'] == 'struct' || type['category'] == 'union' 100 | # structs and unions are handled by the generate:structs task. 101 | else 102 | raise "Unhandled type: #{type.to_s}" 103 | end 104 | end 105 | 106 | resolve_type_aliases.each do |type_alias| 107 | f.puts " typealias '#{type_alias[0]}', '#{type_alias[1]}'" 108 | end 109 | 110 | f.puts 'end' 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /tasks/generate/version.rake: -------------------------------------------------------------------------------- 1 | namespace :generate do 2 | desc "Regenerate Vulkan API version info" 3 | task :version do 4 | header_version = nil 5 | vk_xml.xpath("/registry/types/type[@category='define']").each do |type| 6 | name = type.xpath('name')&.text.to_s 7 | # we are relying on VK_HEADER_VERSION to appear before VK_HEADER_VERSION_COMPLETE 8 | if name == 'VK_HEADER_VERSION' 9 | raise "could not parse header version: #{type.text}" unless type.text =~ /\s+(\d+)$/ 10 | header_version = $1.to_i 11 | elsif name == 'VK_HEADER_VERSION_COMPLETE' 12 | raise "no header version" unless header_version 13 | raise "can't parse version" unless type.text =~ /\((\d+), (\d+), (\d+), VK_HEADER_VERSION/m 14 | full_version = [$1.to_i, $2.to_i, $3.to_i, header_version] 15 | open(generate_dir.join('version.rb'), 'w') do |f| 16 | f.puts header_comment 17 | f.puts 18 | f.puts 'module Vulkan' 19 | f.puts " VK_API_VERSION_VARIANT = #{full_version[0].inspect}" 20 | f.puts " VK_API_VERSION_MAJOR = #{full_version[1].inspect}" 21 | f.puts " VK_API_VERSION_MINOR = #{full_version[2].inspect}" 22 | f.puts " VK_API_VERSION_PATCH = #{header_version.inspect}" 23 | f.puts ' VK_HEADER_VERSION_COMPLETE = [VK_API_VERSION_VARIANT,' 24 | f.puts ' VK_API_VERSION_MAJOR,' 25 | f.puts ' VK_API_VERSION_MINOR,' 26 | f.puts ' VK_API_VERSION_PATCH]' 27 | f.puts 'end' 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /tasks/helpers.rb: -------------------------------------------------------------------------------- 1 | def generate_dir 2 | require 'pathname' 3 | @generate_dir ||= begin 4 | Pathname.new(File.expand_path('../lib/vulkan/generated', __dir__)).tap do |generate_dir| 5 | mkdir_p generate_dir unless generate_dir.directory? 6 | end 7 | end 8 | end 9 | 10 | def vk_xml_path 11 | @vk_xml_path ||= generate_dir.join('vk.xml') 12 | end 13 | 14 | def vk_xml 15 | @vk_xml ||= begin 16 | require 'nokogiri' 17 | Nokogiri::XML(open(vk_xml_path)) 18 | end 19 | end 20 | 21 | def enums 22 | @enums ||= {} 23 | end 24 | 25 | def header_comment 26 | @header_comment ||= <<-end_comment.lines.map(&:strip).join("\n") 27 | # vulkan-ruby #{Vulkan::VERSION} 28 | # 29 | # => https://github.com/sinisterchipmunk/vulkan-ruby 30 | # 31 | # [NOTICE] This is an automatically generated file. 32 | end_comment 33 | end 34 | 35 | def platform_type_map 36 | @platform_type_map ||= { 37 | 'char' => 'Fiddle::TYPE_CHAR', 38 | 'signed char' => 'Fiddle::TYPE_CHAR', 39 | 'unsigned char' => '-Fiddle::TYPE_CHAR', 40 | 'short' => 'Fiddle::TYPE_SHORT', 41 | 'signed short' => 'Fiddle::TYPE_SHORT', 42 | 'unsigned short' => '-Fiddle::TYPE_SHORT', 43 | 'int' => 'Fiddle::TYPE_INT', 44 | 'signed int' => 'Fiddle::TYPE_INT', 45 | 'unsigned int' => '-Fiddle::TYPE_INT', 46 | 'int64_t' => 'Fiddle::TYPE_LONG_LONG', 47 | 'uint64_t' => '-Fiddle::TYPE_LONG_LONG', 48 | 'float' => 'Fiddle::TYPE_FLOAT', 49 | 'double' => 'Fiddle::TYPE_DOUBLE', 50 | 'ptrdiff_t' => 'Fiddle::TYPE_PTRDIFF_T', 51 | 'void' => 'Fiddle::TYPE_VOID', 52 | 'void *' => 'Fiddle::TYPE_VOIDP', 53 | 'int8_t' => 'Fiddle::TYPE_CHAR', 54 | 'int32_t' => 'Fiddle::TYPE_INT', 55 | 'size_t' => 'Fiddle::TYPE_SIZE_T', 56 | 'uint8_t' => '-Fiddle::TYPE_CHAR', 57 | 'uint32_t' => '-Fiddle::TYPE_INT', 58 | } 59 | end 60 | -------------------------------------------------------------------------------- /tasks/shaders.rake: -------------------------------------------------------------------------------- 1 | desc 'recompile ./examples/shaders' 2 | task :shaders do 3 | chdir 'examples/shaders' do 4 | Dir['*.{vert,frag}'].each do |shader| 5 | sh 'glslangValidator', '-V', shader, '-o', "#{shader}.spv" 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/barriers_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class BarriersTest < Minitest::Test 4 | def setup 5 | @instance = Vulkan::Instance.new extensions: [] 6 | @device = @instance.physical_devices.first.create queues: [], extensions: [] 7 | @buffer = @device.create_buffer size: 64, 8 | usage: [ :transfer_dst, :vertex_buffer ], 9 | properties: :device_local 10 | end 11 | 12 | def test_buffer_memory_barrier 13 | # TODO test whether VkBufferMemoryBarrier or VkBufferMemoryBarrier2 is 14 | # used. Detect at runtime which to use depending on Vulkan version 15 | # (VkBufferMemoryBarrier in 1.1-, VkBufferMemoryBarrier2 in 1.2+). 16 | 17 | assert_kind_of Vulkan::BufferMemoryBarrier, @device.create_buffer_memory_barrier(src_access: :transfer_write, 18 | dst_access: :vertex_attribute_read, 19 | buffer: @buffer) 20 | 21 | barrier = @buffer.create_barrier src_access: :transfer_write, 22 | dst_access: :vertex_attribute_read, 23 | offset: 2, 24 | size: 32 25 | assert_kind_of Vulkan::BufferMemoryBarrier, barrier 26 | assert_equal :transfer_write, barrier.src_access 27 | assert_equal :vertex_attribute_read, barrier.dst_access 28 | assert_equal @buffer, barrier.buffer 29 | assert_equal 2, barrier.offset 30 | assert_equal 32, barrier.size 31 | 32 | # make sure it can be used 33 | command_pool = @device.create_command_pool queue_family: 0 34 | command_buffer = command_pool.create_command_buffer 35 | command_buffer.record do |buf| 36 | 37 | buf.pipeline_barriers buffer_barriers: [barrier], 38 | src_stages: :transfer, 39 | dst_stages: :vertex_input 40 | end 41 | end 42 | 43 | def test_memory_barrier 44 | barrier = @device.create_memory_barrier src_access: :transfer_write, 45 | dst_access: :vertex_attribute_read 46 | assert_kind_of Vulkan::MemoryBarrier, barrier 47 | assert_equal :transfer_write, barrier.src_access 48 | assert_equal :vertex_attribute_read, barrier.dst_access 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/conversions_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ConversionsTest < Minitest::Test 4 | include Vulkan::Conversions 5 | 6 | def test_sym_to_samples 7 | assert_equal VK_SAMPLE_COUNT_1_BIT, sym_to_samples( 1) 8 | assert_equal VK_SAMPLE_COUNT_2_BIT, sym_to_samples( 2) 9 | assert_equal VK_SAMPLE_COUNT_4_BIT, sym_to_samples( 4) 10 | assert_equal VK_SAMPLE_COUNT_8_BIT, sym_to_samples( 8) 11 | assert_equal VK_SAMPLE_COUNT_16_BIT, sym_to_samples(16) 12 | assert_equal VK_SAMPLE_COUNT_32_BIT, sym_to_samples(32) 13 | assert_equal VK_SAMPLE_COUNT_64_BIT, sym_to_samples(64) 14 | assert_equal 99, sym_to_samples(99) 15 | end 16 | 17 | def test_array_of_pointers 18 | assert_nil array_of_pointers(nil) 19 | assert_nil array_of_pointers([]) 20 | end 21 | 22 | def test_array_of_structures 23 | assert_nil array_of_structures(nil) 24 | assert_nil array_of_structures([]) 25 | end 26 | 27 | def test_queue_family_to_index 28 | assert_equal 1, queue_family_to_index(1) 29 | assert_equal 1, queue_family_to_index({ index: 1 }) 30 | assert_equal VK_QUEUE_FAMILY_IGNORED, queue_family_to_index(nil) 31 | assert_raises(ArgumentError) { queue_family_to_index(Object.new) } 32 | end 33 | 34 | def test_syms_to_image_usage_flags 35 | assert_equal VK_IMAGE_USAGE_TRANSFER_SRC_BIT, syms_to_image_usage_flags([:transfer_src]) 36 | assert_equal VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, 37 | syms_to_image_usage_flags([:transfer_src, :depth_stencil_attachment]) 38 | assert_equal(-1, syms_to_image_usage_flags(-1)) 39 | assert_equal 1 | 2 | 4, syms_to_image_usage_flags([1, 2, 4]) 40 | assert_equal VK_IMAGE_USAGE_TRANSFER_SRC_BIT, syms_to_image_usage_flags(:transfer_src) 41 | end 42 | 43 | def test_sym_to_image_tiling 44 | assert_equal VK_IMAGE_TILING_OPTIMAL, sym_to_image_tiling(:optimal) 45 | assert_equal VK_IMAGE_TILING_LINEAR, sym_to_image_tiling(:linear) 46 | assert_equal 100, sym_to_image_tiling(100) 47 | end 48 | 49 | def test_sym_to_image_type 50 | assert_equal VK_IMAGE_TYPE_1D, sym_to_image_type(:'1d') 51 | assert_equal VK_IMAGE_TYPE_2D, sym_to_image_type(:'2d') 52 | assert_equal VK_IMAGE_TYPE_3D, sym_to_image_type(:'3d') 53 | assert_equal 100, sym_to_image_type(100) 54 | end 55 | 56 | def test_sym_to_image_format 57 | assert_equal VK_FORMAT_R8G8B8A8_UNORM, sym_to_image_format(:r8g8b8a8_unorm) 58 | assert_equal VK_FORMAT_R32G32B32A32_UINT, sym_to_image_format(:r32g32b32a32_uint) 59 | assert_equal VK_FORMAT_ASTC_8x6_UNORM_BLOCK, sym_to_image_format(:astc_8x6_unorm_block) 60 | assert_equal(-1, sym_to_image_format(-1)) 61 | end 62 | 63 | def test_descriptor_set_layout_types 64 | assert_equal VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR, 65 | syms_to_descriptor_set_layout_type_flags([:push]) 66 | 67 | assert_equal VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT_EXT, 68 | syms_to_descriptor_set_layout_type_flags([:update_after_bind_pool]) 69 | 70 | assert_equal VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR | 71 | VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT_EXT, 72 | syms_to_descriptor_set_layout_type_flags([:push, :update_after_bind_pool]) 73 | end 74 | 75 | def test_shader_stage_flags 76 | assert_equal VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 77 | syms_to_shader_stage_flags([:vertex, :geometry, :fragment]) 78 | assert_equal VK_SHADER_STAGE_VERTEX_BIT, syms_to_shader_stage_flags([:vertex]) 79 | assert_equal VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, syms_to_shader_stage_flags([:tessellation_control]) 80 | assert_equal VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, syms_to_shader_stage_flags([:tessellation_evaluation]) 81 | assert_equal VK_SHADER_STAGE_GEOMETRY_BIT, syms_to_shader_stage_flags([:geometry]) 82 | assert_equal VK_SHADER_STAGE_FRAGMENT_BIT, syms_to_shader_stage_flags([:fragment]) 83 | assert_equal VK_SHADER_STAGE_COMPUTE_BIT, syms_to_shader_stage_flags([:compute]) 84 | assert_equal VK_SHADER_STAGE_ALL_GRAPHICS, syms_to_shader_stage_flags([:all_graphics]) 85 | assert_equal VK_SHADER_STAGE_ALL, syms_to_shader_stage_flags([:all]) 86 | assert_equal VK_SHADER_STAGE_TASK_BIT_NV, syms_to_shader_stage_flags([:task]) 87 | assert_equal VK_SHADER_STAGE_MESH_BIT_NV, syms_to_shader_stage_flags([:mesh]) 88 | assert_equal VK_SHADER_STAGE_RAYGEN_BIT_NV, syms_to_shader_stage_flags([:raygen]) 89 | assert_equal VK_SHADER_STAGE_ANY_HIT_BIT_NV, syms_to_shader_stage_flags([:any_hit]) 90 | assert_equal VK_SHADER_STAGE_CLOSEST_HIT_BIT_NV, syms_to_shader_stage_flags([:closest_hit]) 91 | assert_equal VK_SHADER_STAGE_MISS_BIT_NV, syms_to_shader_stage_flags([:miss]) 92 | assert_equal VK_SHADER_STAGE_INTERSECTION_BIT_NV, syms_to_shader_stage_flags([:intersection]) 93 | assert_equal VK_SHADER_STAGE_CALLABLE_BIT_NV, syms_to_shader_stage_flags([:callable]) 94 | end 95 | 96 | def test_descriptor_types 97 | assert_equal VK_DESCRIPTOR_TYPE_SAMPLER, sym_to_descriptor_type(:sampler) 98 | assert_equal VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, sym_to_descriptor_type(:combined_image_sampler) 99 | assert_equal VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, sym_to_descriptor_type(:sampled_image) 100 | assert_equal VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, sym_to_descriptor_type(:storage_image) 101 | assert_equal VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, sym_to_descriptor_type(:uniform_texel_buffer) 102 | assert_equal VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, sym_to_descriptor_type(:storage_texel_buffer) 103 | assert_equal VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, sym_to_descriptor_type(:uniform_buffer) 104 | assert_equal VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, sym_to_descriptor_type(:storage_buffer) 105 | assert_equal VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, sym_to_descriptor_type(:uniform_buffer_dynamic) 106 | assert_equal VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, sym_to_descriptor_type(:storage_buffer_dynamic) 107 | assert_equal VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, sym_to_descriptor_type(:input_attachment) 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /test/devices_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class VulkanDevicesTest < Minitest::Test 4 | def setup 5 | @instance = Vulkan::Instance.new extensions: [] 6 | end 7 | 8 | end 9 | -------------------------------------------------------------------------------- /test/dispatch_table_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class DispatchTableTest < Minitest::Test 4 | def setup 5 | @instance = Vulkan::Instance.new extensions: [] 6 | @device = @instance.physical_devices.first.create queues: [], extensions: [] 7 | end 8 | 9 | def test_maintain_instance_and_device_handles 10 | dispatch_table = Vulkan[@instance, @device] 11 | 12 | # DispatchTable is the main interface passed around to other objects 13 | # (Vulkan::Semaphore, etc). Those objects need to be able to call `vk*` 14 | # methods to do stuff. If nothing else, they need it for GC purposes 15 | # so they can clean up after themselves. Now, by definition, a 16 | # DispatchTable relies on the instance and device that it was constructed 17 | # with. Internally, however, it technically only relies on the pointers to 18 | # the opaque handles (`void *`) of those objects, as returned by the 19 | # original calls to `vkCreate*`. So it might seem reasonable for 20 | # DispatchTable to eagerly coerce the instance and device into those low 21 | # level memory pointers, for a performance gain. It's also an 22 | # implementation detail that we wouldn't typically unit test. However, 23 | # the result of doing this (or anything like it) is that it drops the 24 | # references to the Vulkan::Instance and Vulkan::LogicalDevice objects 25 | # in Ruby. If the client code doesnt *also* keep those handles around, 26 | # it makes those objects eligible for garbage collection. This means that 27 | # the instance and/or device could be freed / destroyed while some other 28 | # object is still using them. So although at first glance it looks like 29 | # an implementation detail that a consumer of the DispatchTable shouldn't 30 | # need to care about, it's actually critical that DispatchTable maintains 31 | # a reference to those original objects. 32 | 33 | ivars = dispatch_table.instance_variables.map do |ivar_name| 34 | dispatch_table.instance_variable_get(ivar_name) 35 | end 36 | assert_includes ivars, @instance 37 | assert_includes ivars, @device 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/generators_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class GeneratorsTest < Minitest::Test 4 | def test_VkImageBlit_offsets_are_arrays 5 | assert_equal 2, VkImageBlit.malloc.srcOffsets.size 6 | assert_equal 2, VkImageBlit.malloc.dstOffsets.size 7 | end 8 | 9 | def test_version 10 | assert_kind_of Array, VK_HEADER_VERSION_COMPLETE 11 | assert_equal 4, VK_HEADER_VERSION_COMPLETE.size 12 | # current version at time of writing 13 | assert VK_HEADER_VERSION_COMPLETE[0] >= 0 14 | assert VK_HEADER_VERSION_COMPLETE[1] >= 1 15 | assert VK_HEADER_VERSION_COMPLETE[2] >= 3 16 | assert VK_HEADER_VERSION_COMPLETE[3] >= 207 17 | end 18 | 19 | def test_uint32_pointer_in_swapchain_create_info_struct 20 | content = File.read(Vulkan.root.join('generated/structs.rb')) 21 | content[/VkSwapchainCreateInfoKHR\s*=\s*struct\s*\[(.*?)\]/m] 22 | struct_content = $1 23 | refute_nil struct_content 24 | assert_match(/uint32_t\s*\*\s*pQueueFamilyIndices/, struct_content) 25 | end 26 | 27 | def test_structs_containing_arrays_with_enum_sizes 28 | content = File.read(Vulkan.root.join('generated/structs.rb')) 29 | content[/VkPhysicalDeviceMemoryProperties\s*=\s*struct\s*\[(.*?)\]$/m] 30 | struct_content = $1 31 | refute_nil struct_content 32 | assert_match(/memoryTypes\s*\[.*?=>\s*VkMemoryType/, struct_content) 33 | end 34 | 35 | def test_extensions_discovered 36 | assert_equal "VK_NV_ray_tracing", VK_NV_RAY_TRACING_EXTENSION_NAME 37 | end 38 | 39 | def test_extension_enums_have_correct_values 40 | assert_equal 1000001000, VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR 41 | assert_equal -1000001004, VK_ERROR_OUT_OF_DATE_KHR 42 | end 43 | 44 | def test_commands_and_params 45 | content = File.read(Vulkan.root.join('generated/commands.rb')) 46 | assert content[/register_function\s*['"]VkResult vkCreateInstance['"]\s*,\s*['"]const VkInstanceCreateInfo\* pCreateInfo, const VkAllocationCallbacks\* pAllocator, VkInstance\* pInstance['"]/] 47 | end 48 | 49 | def test_enums 50 | assert Vulkan.struct(['VkAttachmentLoadOp i']).malloc 51 | assert Vulkan.struct(['GgpFrameToken i']).malloc 52 | assert_equal 1000.0, VK_LOD_CLAMP_NONE 53 | end 54 | 55 | def test_structs_containing_arrays 56 | content = File.read(Vulkan.root.join('generated/structs.rb')) 57 | assert content[/VkClearColorValue\s*=\s*union\s*\["float\s*float32\s*\[4\]",/] 58 | end 59 | 60 | def test_types 61 | content = File.read(Vulkan.root.join('generated/types.rb')) 62 | assert content[/typealias\s*['"]VkSampleMask['"]\s*,\s*['"]uint32_t['"]/] 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/logical_device_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class LogicalDeviceTest < Minitest::Test 4 | include Conversions 5 | 6 | def test_cannot_activate_an_unsupported_feature 7 | instance = Vulkan::Instance.new extensions: [] 8 | physical = instance.physical_devices.first 9 | # test is invalid on hardware that supports *everything* 10 | return if physical.unsupported_features.empty? 11 | 12 | assert_raises Vulkan::Error::UnsupportedFeature do 13 | physical.create queues: [], extensions: [], features: physical.unsupported_features 14 | end 15 | end 16 | 17 | def test_disabling_a_feature_does_not_make_it_unsupported 18 | instance = Vulkan::Instance.new extensions: [] 19 | physical = instance.physical_devices.first 20 | # test is invalid on hardware that supports *nothing* 21 | return if physical.supported_features.empty? 22 | logical = physical.create queues: [], extensions: [], features: [] 23 | 24 | refute physical.supported_features.empty? 25 | refute logical.feature_enabled?(physical.supported_features.first) 26 | assert struct_to_hash(physical.features)[physical.supported_features.first] == VK_TRUE 27 | end 28 | 29 | def test_enabled_features 30 | instance = Vulkan::Instance.new extensions: [] 31 | physical = instance.physical_devices.first 32 | device = physical.create queues: [], extensions: [], features: physical.supported_features 33 | 34 | assert_kind_of Array, device.enabled_features 35 | supported_features = device.physical_device.supported_features 36 | unsupported_features = device.physical_device.unsupported_features 37 | device.enabled_features.each do |feature| 38 | assert supported_features.include?(feature) 39 | refute unsupported_features.include?(feature) 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/pipelines_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class PipelinesTest < Minitest::Test 4 | def setup 5 | @instance = Vulkan::Instance.new extensions: [] 6 | @device = @instance.physical_devices.first.create queues: [], extensions: ['VK_KHR_push_descriptor'] 7 | end 8 | 9 | def test_descriptors_and_layouts 10 | pipeline = @device.create_pipeline(viewport: { width: 640, height: 480 }) 11 | descriptor = { 12 | binding: 0, 13 | type: :uniform_buffer, 14 | stages: [:vertex] 15 | } 16 | layout = pipeline.add_descriptor_set_layout descriptors: [descriptor] 17 | assert_equal 0, layout.descriptors[0].binding 18 | assert_equal 1, layout.descriptors[0].descriptorCount 19 | assert_equal VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, layout.descriptors[0].descriptorType 20 | assert_equal VK_SHADER_STAGE_VERTEX_BIT, layout.descriptors[0].stageFlags 21 | 22 | descriptor = { 23 | binding: 2, 24 | type: :uniform_buffer, 25 | stages: [:vertex], 26 | count: 10 27 | } 28 | 29 | layout = pipeline.add_descriptor_set_layout descriptors: [descriptor] 30 | assert_equal 2, layout.descriptors[0].binding 31 | assert_equal 10, layout.descriptors[0].descriptorCount 32 | assert_equal 0, layout.build_layout_info.flags 33 | assert_equal 1, layout.build_layout_info.bindingCount 34 | 35 | layout = pipeline.add_descriptor_set_layout :push, descriptors: [descriptor] 36 | assert_equal VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR, layout.build_layout_info.flags 37 | 38 | refute_empty pipeline.layouts 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/semaphores_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class VulkanSemaphoresTest < Minitest::Test 4 | def setup 5 | @instance = Vulkan::Instance.new extensions: [] 6 | @device = @instance.physical_devices.first.create queues: [], extensions: [] 7 | end 8 | 9 | def test_create_semaphores 10 | assert_kind_of Vulkan::Semaphore, @device.create_semaphore 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/struct_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class VulkanStructTest < Minitest::Test 4 | def test_offsetof 5 | s = Vulkan.struct(['int x', 'int y']) 6 | assert_nil s.offsetof('missing') 7 | assert_equal 0, s.offsetof('x') 8 | assert_equal Fiddle::SIZEOF_INT, s.offsetof('y') 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /test/swapchain_builder_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class SwapchainBuilderTest < Minitest::Test 4 | include Vulkan 5 | 6 | def test_choosing_presentation_mode 7 | # Degrade from best quality to worst quality, but allow app config to 8 | # override as long as the override option is supported. 9 | 10 | surface_info = Mock::SwapchainSurfaceInfo.new presentation_modes: [:fifo, :immediate, :mailbox, :fifo_relaxed] 11 | swapchain = SwapchainBuilder.new(surface_info) 12 | # triple buffered, should never tear 13 | assert_equal :mailbox, swapchain.presentation_mode 14 | 15 | surface_info = Mock::SwapchainSurfaceInfo.new presentation_modes: [:fifo, :immediate, :fifo_relaxed] 16 | swapchain = SwapchainBuilder.new(surface_info) 17 | # double buffered - force vsync, always show most recent frame. Should 18 | # never tear, may perform worse than mailbox but uses less memory. 19 | assert_equal :fifo, swapchain.presentation_mode 20 | 21 | surface_info = Mock::SwapchainSurfaceInfo.new presentation_modes: [:fifo_relaxed, :immediate] 22 | swapchain = SwapchainBuilder.new(surface_info) 23 | # as with fifo, but tearing is allowed after first vsync - may tear if 24 | # framerate is lower than refresh rate 25 | assert_equal :fifo_relaxed, swapchain.presentation_mode 26 | 27 | surface_info = Mock::SwapchainSurfaceInfo.new presentation_modes: [:immediate] 28 | swapchain = SwapchainBuilder.new(surface_info) 29 | # no buffering, display frame immediately. Will probably tear. 30 | assert_equal :immediate, swapchain.presentation_mode 31 | 32 | # test app config overrides 33 | surface_info = Mock::SwapchainSurfaceInfo.new presentation_modes: [:fifo, :immediate, :mailbox, :fifo_relaxed] 34 | swapchain = SwapchainBuilder.new(surface_info, graphics: { swapchain: { vsync: false } }) 35 | assert_equal :immediate, swapchain.presentation_mode 36 | 37 | surface_info = Mock::SwapchainSurfaceInfo.new presentation_modes: [:fifo, :immediate, :mailbox, :fifo_relaxed] 38 | swapchain = SwapchainBuilder.new(surface_info, graphics: { swapchain: { presentation_mode: :fifo_relaxed } }) 39 | assert_equal :fifo_relaxed, swapchain.presentation_mode 40 | 41 | surface_info = Mock::SwapchainSurfaceInfo.new presentation_modes: [:fifo, :immediate, :mailbox, :fifo_relaxed] 42 | swapchain = SwapchainBuilder.new(surface_info, graphics: { swapchain: { presentation_mode: :invalid } }) 43 | assert_equal :mailbox, swapchain.presentation_mode 44 | 45 | assert_raises do 46 | surface_info = Mock::SwapchainSurfaceInfo.new presentation_modes: [] 47 | swapchain = SwapchainBuilder.new(surface_info) 48 | swapchain.presentation_mode 49 | end 50 | end 51 | 52 | def test_choosing_number_of_swapchain_images 53 | surface_info = Mock::SwapchainSurfaceInfo.new min_image_count: 1, max_image_count: 1 54 | swapchain = SwapchainBuilder.new(surface_info) 55 | assert_equal 1, swapchain.image_count 56 | 57 | surface_info = Mock::SwapchainSurfaceInfo.new min_image_count: 1, max_image_count: 0 58 | swapchain = SwapchainBuilder.new(surface_info) 59 | assert_equal 2, swapchain.image_count 60 | 61 | surface_info = Mock::SwapchainSurfaceInfo.new min_image_count: 1, max_image_count: 10 62 | swapchain = SwapchainBuilder.new(surface_info) 63 | assert_equal 2, swapchain.image_count 64 | end 65 | 66 | def test_usage_support 67 | surface_info = Mock::SwapchainSurfaceInfo.new supported_usage: [:transfer_dst] 68 | swapchain = SwapchainBuilder.new(surface_info) 69 | assert swapchain.usage_supported? 70 | 71 | assert_raises do 72 | surface_info = Mock::SwapchainSurfaceInfo.new supported_usage: [] 73 | swapchain = SwapchainBuilder.new(surface_info) 74 | end 75 | end 76 | 77 | def test_transformation 78 | # use identity by default 79 | surface_info = Mock::SwapchainSurfaceInfo.new supported_transforms: [:identity, :rotate_90], 80 | current_transform: [:rotate_90] 81 | swapchain = SwapchainBuilder.new(surface_info) 82 | assert_equal :identity, swapchain.transformation 83 | 84 | # fail over to whatever is currently selected 85 | surface_info = Mock::SwapchainSurfaceInfo.new supported_transforms: [], 86 | current_transform: [:rotate_90] 87 | swapchain = SwapchainBuilder.new(surface_info) 88 | assert_equal [:rotate_90], swapchain.transformation 89 | end 90 | 91 | def test_choosing_swapchain_image_dimensions 92 | surface_info = Mock::SwapchainSurfaceInfo.new current_extent: { width: 800, height: 600 }, 93 | min_image_extent: { width: 320, height: 200 }, 94 | max_image_extent: { width: 8192, height: 8192 } 95 | swapchain = SwapchainBuilder.new(surface_info) 96 | assert_equal [1024, 768], swapchain.dimensions(1024, 768) 97 | 98 | swapchain = SwapchainBuilder.new(surface_info) 99 | assert_equal [320, 200], swapchain.dimensions(0, 0) 100 | 101 | swapchain = SwapchainBuilder.new(surface_info) 102 | assert_equal [8192, 8192], swapchain.dimensions(10000, 10000) 103 | 104 | surface_info.capabilities[:current_extent] = { width: -1, height: -1 } 105 | swapchain = SwapchainBuilder.new(surface_info, window: { width: 2048, height: 1536 }) 106 | assert_equal [2048, 1536], swapchain.dimensions 107 | 108 | surface_info.capabilities[:current_extent] = { width: -1, height: -1 } 109 | swapchain = SwapchainBuilder.new(surface_info, graphics: { swapchain: { width: 2048, height: 1536 } }) 110 | assert_equal [2048, 1536], swapchain.dimensions 111 | 112 | surface_info.capabilities[:current_extent] = { width: -1, height: -1 } 113 | surface_info.capabilities[:min_image_extent] = { width: 1024, height: 768 } 114 | swapchain = SwapchainBuilder.new(surface_info) 115 | assert_equal [1024, 768], swapchain.dimensions 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'simplecov' 3 | SimpleCov.start 4 | 5 | require "vulkan" 6 | require 'vulkan/mock' 7 | 8 | require "minitest/autorun" 9 | 10 | class Minitest::Test 11 | include Vulkan 12 | end 13 | -------------------------------------------------------------------------------- /test/vulkan_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class VulkanTest < Minitest::Test 4 | def test_that_it_has_a_version_number 5 | refute_nil ::Vulkan::VERSION 6 | end 7 | 8 | def test_available_extensions 9 | assert_kind_of Array, Vulkan::Instance.extensions 10 | end 11 | 12 | def test_available_layers 13 | assert_kind_of Array, Vulkan::Instance.layers 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /vulkan-ruby.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path("../lib", __FILE__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require "vulkan/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "vulkan-ruby" 7 | spec.version = Vulkan::VERSION 8 | spec.authors = ["Colin MacKenzie IV"] 9 | spec.email = ["sinisterchipmunk@gmail.com"] 10 | 11 | spec.summary = %q{Vulkan bindings for Ruby} 12 | spec.homepage = "https://github.com/sinisterchipmunk/vulkan-ruby" 13 | spec.license = "MIT" 14 | 15 | # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host' 16 | # to allow pushing to a single host or delete this section to allow pushing to any host. 17 | if spec.respond_to?(:metadata) 18 | spec.metadata["allowed_push_host"] = "https://www.rubygems.org" 19 | else 20 | raise "RubyGems 2.0 or newer is required to protect against " \ 21 | "public gem pushes." 22 | end 23 | 24 | # Specify which files should be added to the gem when it is released. 25 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 26 | spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do 27 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 28 | end 29 | spec.bindir = "exe" 30 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 31 | spec.require_paths = ["lib"] 32 | 33 | spec.required_ruby_version = '>= 3.1.1' 34 | spec.add_development_dependency "bundler", "~> 2.1" 35 | spec.add_development_dependency "rake", "~> 13.0" 36 | spec.add_development_dependency "minitest", "~> 5.15" 37 | spec.add_development_dependency 'simplecov', '~> 0.16' 38 | spec.add_development_dependency "nokogiri", '~> 1.8' # used to process vk.xml 39 | spec.add_development_dependency 'sdl2_vulkan', '~> 0.1' # used by the examples 40 | spec.add_development_dependency 'cglm', '~> 0.1' # used by the examples 41 | spec.add_development_dependency 'chunky_png', '~> 1.3' # used by the examples 42 | spec.add_development_dependency 'oily_png', '~> 1.2' # used by the examples 43 | spec.add_development_dependency 'tiny_obj', '~> 0.2' # used by the examples 44 | spec.add_dependency 'sorted_set' 45 | end 46 | --------------------------------------------------------------------------------