GithubHelp home page GithubHelp logo

anvil's Introduction

Anvil

This is a README file for Anvil, a framework for Vulkan™. The README is organized as a FAQ.

Toolchain Status
Linux (clang/gcc) build status Build Status
Windows (VS15/VS17) build status: Build status

What license governs Anvil usage?

MIT. See LICENSE.txt.

What is this?

Anvil is a framework for Vulkan v1.0 and v1.1, which we have been using internally for quite some time now, in order to develop various Vulkan applications. This guarantees that vast majority of the functionality exposed by the library is regularly tested.

The general idea we started from was to have a cross-platform tool, which would reduce the amount of time required to write portable Vulkan-based apps from scratch. We would then find ourselves adding new features & extending the existing codebase with new wrappers every now and then.

This eventually led to the library we have decided to release to the public.

Why? Do we really need another wrapper library for Vulkan?

Anvil was designed with the following goals in mind:

  • Provide object-oriented Vulkan solution.
  • Reduce the amount of code the developer needs to write in order to start using Vulkan, without hiding the API behind thick abstraction layers.
  • Simplify validation layer usage. All you have to do is specify which function you would like to be called if a debug call-back is made, and that's it.
  • Provide a simple cross-platform implementation for areas unrelated to Vulkan (eg. window management)
  • Provide a simple way (with optional flexibility) to manage memory allocations and memory bindings.

Anvil is not the right choice for developers who do not have a reasonable understanding of how Vulkan works. Its goal is not to provide a glBegin/glEnd-like level of abstraction, but rather to give a sensible environment, in which you can rapidly prototype Vulkan applications.

What platforms and hardware does it work on?

Currently, Anvil has been confirmed to build and work correctly under:

  • 32- and 64-bit Linux (Ubuntu)
  • 32- and 64-bit Windows (7, 8.1, 10)

What are Anvil's requirements?

In order to build Anvil, you will need the following software:

  • C++11 compiler.
  • CMake
  • Vulkan SDK (the latest available version is highly recommended)

To build Anvil on Linux, additional packages should be installed:

  • libxcb-keysyms (For ubuntu, use "apt-get install libxcb-keysyms1-dev")

Does Anvil work only with AMD driver?

Anvil has not been designed with AMD hardware architecture in mind, but it is going to provide interface-level support for any extensions we may decide to release in the future.

As such, it should (and has been verified to) work on any Windows Vulkan implementation and on AMDGPU-PRO Vulkan implementation on Linux.

What can it do?

Anvil provides full support for functionality exposed in Vulkan 1.0 and Vulkan 1.1. We also try to do our best to keep it up to date with any extensions our Vulkan implementations expose.

We are planning to keep adding new features in the future.

Are there any Anvil examples available which would present how to use the framework?

Anvil comes with several example applications, including OutOfOrderRasterization, which renders 10k teapots on screen. It uses various Anvil wrappers, so you can use it to get a better understanding of how various parts of the library can be used.

...and its Vulkan-related code only takes ~45kbytes!

OutOfOrderRasterization and the other example applications are located in the examples directory.

What are the known issues?

Please observe the "Issues" tab for more details.

Who made it?

Various developers within AMD.

What if I have some feed-back?

Please feel free to open an issue in Anvil's GitHub project.

Attribution

  • AMD, the AMD Arrow logo, Radeon, and combinations thereof are either registered trademarks or trademarks of Advanced Micro Devices, Inc. in the United States and/or other countries.
  • Microsoft, Visual Studio, and Windows are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries.
  • Linux is the registered trademark of Linus Torvalds in the U.S. and other countries.
  • Vulkan and the Vulkan logo are trademarks of the Khronos Group, Inc.

anvil's People

Contributors

arkadiuszsarwa avatar atomsymbol-notifications avatar christophhaag avatar dominikwitczakamd avatar essgeeeich avatar janisozaur avatar jstewart-amd avatar kbielude avatar mesonnaise avatar razvan1024 avatar silverlan avatar thepra avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

anvil's Issues

Exception when trying to write directly to a buffer with a parent

Example:

auto bufParent = Anvil::Buffer::create_nonsparse(
	dev,
	1'000ull, /* size */
	Anvil::QueueFamily::QUEUE_FAMILY_GRAPHICS_BIT,static_cast<VkSharingMode>(vk::SharingMode::eExclusive),
	static_cast<VkBufferUsageFlags>(vk::BufferUsageFlagBits::eUniformBuffer),
	Anvil::MemoryFeatureFlagBits::MEMORY_FEATURE_FLAG_MAPPABLE | Anvil::MemoryFeatureFlagBits::MEMORY_FEATURE_FLAG_HOST_CACHED,
	nullptr
);
std::array<uint8_t,100> data;
auto bufChild = Anvil::Buffer::create_nonsparse(
	bufParent,
	0ull, /* start offset */
	data.size() *sizeof(data.front())
);
bufParent->write(0ull,data.size() *sizeof(data.front()),data.data()); // Works
bufChild->get_memory_block(0u)->write(0ull,data.size() *sizeof(data.front()),data.data()); // Works
bufChild->write(0ull,data.size() *sizeof(data.front()),data.data()); // std::bad_weak_ptr exception

The problem is that the m_device_ptr of the child-buffer is not being initialized, resulting in a std::bad_weak_ptr exception whenever a method is called that needs it.

Suggestion: Switch to vulkan.hpp

Anvil is C++ only anyway, so I don't see much of a reason to stick to the C-header. Most C++-programmers (including me) would probably be more comfortable with the provided type safety of vulkan.hpp.

Rethink Ownership Model for Anvil Objects?

Hey Dominik,

I'm not so sure this is something that can be easily done overnight, but after working with Anvil a little bit I'm thinking that its ownership model is reversed from what it ought-to-be.

For example, when I create an Anvil Device, I receive a weak_ptr<SGPUDevice>. That seems fairly weird to me - I literally just made this thing and I don't even actually own it?

And then I got to thinking, "Hey wait - if I only got back a weak_ptr, then how does this object still even exist? Does somebody else have a sneaky shared_ptr that I don't know about?"

And the answer is "yes" - Behind the scenes, it registered itself with the PhysicalDevice it was created from. I found that a little non-obvious given how I thought the ownership model would work, but now I see that it looks like the intended ownership model is that an "Instance-owns-a-PhysicalDevice-which-owns-a-SGPUDevice".

I think I understand what the intuition was that drove this design - That you want to make sure that if the Device dies, for example, it will also kill everything downstream that relies on this - but I am wondering if that model needs to be inverted?

That is - Devices should hold shared_ptrs to their PhysicalDevices, and PhysicalDevices should hold shared_ptrs to their instances. And when I create an object, I should hold a shared_ptr to the object I just created.

I think in this model, it makes sense - If I create a Device and you give me the only shared_ptr to it back, and I let that shared_ptr go out-of-scope, you should be cleaning up that device because nobody can access it ever again.

And if that Device was the last device that had a shared_ptr to a certain PhysicalDevice, then you should release that PhysicalDevice, as nobody has a reference to it anymore.

And so forth with the Instance.

Anyway - I'm not totally sure I'm not missing something, but I am curious to hear your thoughts on that. I suspect it's unfortunately not a trivial effort to fix this if it is, in fact, a problem.

Use std::function instead of C-style Callback/UserPtr mechanism

I believe there are a few places this is done in Anvil (Keypress callbacks, validation callbacks, window drawFrame callbacks), but C++11 introduced std::function to STL, which creates a typesafe alternative to the old, type-unsafe, memory-leaky, C-style callback mechanism of passing a "Function Pointer + (void* userArgs)" pair and holding your nose while you use reinterpret_cast<>. It also accepts Functors, Lambda Expressions, Pointer-to-member-functions, etc, which are starting to become much more prominent in modern C++ code.

It would be nice down-the-road to see all instances of callbacks used in Anvil to adopt this newer mechanism.

Anvil build creates 'config.h' in source tree, not build tree

I haven't investigated yet (if I get a chance, I'll update further), but I've noticed that Anvil creates include/config.h in the source tree instead of the build tree. This is probably not the desired behavior, and leads to a bit of an annoyance because Git wants me to add the file even though it's generated.

Examples: missing #include <math.h>

[ 99%] Building CXX object CMakeFiles/MultiViewport.dir/src/app.cpp.o
Anvil/examples/MultiViewport/src/app.cpp: In member function ‘std::shared_ptr<unsigned char> App::get_mesh_data() const’:
Anvil/examples/MultiViewport/src/app.cpp:322:85: error: ‘sin’ was not declared in this scope
             x = sin(float(n_vertex) / float(N_SUBDIVISION_TRIANGLES - 1) * 2.0f * pi);
                                                                                     ^
Anvil/examples/MultiViewport/src/app.cpp:323:85: error: ‘cos’ was not declared in this scope
             y = cos(float(n_vertex) / float(N_SUBDIVISION_TRIANGLES - 1) * 2.0f * pi);
                                                                                     ^
Anvil/examples/MultiViewport/src/app.cpp:335:69: error: ‘sin’ was not declared in this scope
             *color_data_ptr = sin(float(x * 25.0f + n_color_stream) )                                           * 0.5f + 0.5f; ++color_data_ptr;
                                                                     ^
Anvil/examples/MultiViewport/src/app.cpp:336:69: error: ‘cos’ was not declared in this scope
             *color_data_ptr = cos(float(y * 71.0f + n_color_stream) )                                           * 0.5f + 0.5f; ++color_data_ptr;

It is almost meaningless to use VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT as the srcStageMask in a barrier

In Image.h, there are 2 spots where pipeline barriers are used:

  • To transition the image to "transfer dest" format so the mipmap data can be transferred to it
  • To transition the image to its final layout once that's done

In both situations, Anvil currently uses VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT as the srcStageMask. Unfortunately, doing this doesn't accomplish anything. According to the Vulkan Spec:

An execution dependency with only VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT in the 
destination stage mask will only prevent that stage from executing in subsequently 
submitted commands. As this stage does not perform any actual execution, this is not 
observable - in effect, it does not delay processing of subsequent commands. Similarly an 
execution dependency with only VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT in the source stage 
mask will effectively not wait for any prior commands to complete.

When defining a memory dependency, using only VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT 
or VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT would never make any accesses available and/or
 visible because these stages do not access memory.

The validation layers also complain about it with the following message:

[!] vkCmdPipelineBarrier(): pImageMemBarriers[0].srcAccessMask (0x5000) is not supported 
by srcStageMask (0x1). The spec valid usage text states 'Each element of 
pMemoryBarriers, pBufferMemoryBarriers and pImageMemoryBarriers must not have any 
access flag included in its srcAccessMask member if that bit is not supported by any of the 
pipeline stages in srcStageMask, as specified in the table of supported access types.' 
(https://www.khronos.org/registry/vulkan/specs/1.0/html/vkspec.html
#VUID-vkCmdPipelineBarrier-pMemoryBarriers-01184)

I believe that it is okay to do this for the first barrier, since there is no dependency since the image is new and has never been used before, but the one that transitions to final layout definitely needs to be fixed.

Change supported NT version to something more reasonable

From Anvil/include/misc/types.h:

#define ANVIL_MIN_WIN32_WINNT_REQUIRED 0x0400

4.0 is the kernel for "Windows NT 4.0", an operating system that was released in 1996. I am fairly certain that Anvil and Vulkan will not work on such a machine (nor should it honestly try to :-S )

I suggest to set this to something a bit more reasonable - Windows XP would be 5.1, but I'm guessing that the earliest OS that Anvil is realistically tested on is probably Windows 7, which would be NT version 6.1

PS:

The reason I care about this is because I'm using the RawInput API, which is not available before Windows XP, and I couldn't figure out for a while why the symbols I needed weren't available while using Anvil

It's not allowed to transition swapchain images without first acquiring them

https://github.com/GPUOpen-LibrariesAndSDKs/Anvil/blob/f50cfeb203b500bd3a95ba17b9f9c8fa84c49ec5/src/wrappers/swapchain.cpp#L325

Here you setup a resource transition for each image in the swapchain which you run during initialize without acquiring them. This is not allowed.

https://www.khronos.org/registry/vulkan/specs/1.0-wsi_extensions/xhtml/vkspec.html#_wsi_swapchain

Use of a presentable image must occur only after the image is returned by vkAcquireNextImageKHR, and before it is presented by vkQueuePresentKHR. This includes transitioning the image layout and rendering commands.

Instead you can set the initialLayout of the renderpass attachment to VK_IMAGE_LAYOUT_UNDEFINED. Implying that you don't care about the existing data (which can then be made explicit by a load_op_clear). This will need to happen in the usage code.

For more info see http://stackoverflow.com/q/37524032/731620

Documentation

I would really like to see a comprehensive documentation or at least a proper guide.
Vulkan is pretty complex for beginners and this Wrapper seems to provide the much needed simplification needed to write efficient code, especially for starters. But without a real documentation it is not really of use for this group. I have to admit that I am not really sure wether those kind of people are targeted.
Non the less it would be really kind to provide some more help, not just examples.

The way Anvil uses assertions is inconsistent and dangerous

Generally, asserts are used in code for things that should never happen and can't reasonably be recovered from, where allowing the program to continue running would be invalid. Logging and calling abort() is generally recognized as a perfectly-reasonable resolution to triggering one.

But it seems like in Anvil, the use of anvil_assert() is used for both cases where reasonable recovery is not possible, but also just for general errors that need to be reported.

This is true even in the same function within a few lines of each other. Using Queue::present() as an example:

Line #227:

    /* Sanity checks */
    anvil_assert(in_n_wait_semaphores < sizeof(wait_semaphores_vk) / sizeof(wait_semaphores_vk[0]) );

This is an assert that cannot fail. If it does, code later on will cause a buffer overflow.

But then later on in the same function:

Line #284:

    result = swapchain_entrypoints.vkQueuePresentKHR(m_queue,
                                                    &image_presentation_info);

    anvil_assert_vk_call_succeeded(result);

    if (is_vk_call_successful(result) )
    {
        anvil_assert(is_vk_call_successful(presentation_results[0]));

        /* Return the most important error code reported */
        switch (presentation_results[0])
        {
    ...

It's perfectly-reasonable for that assert to fail, and for the program to handle the error and continue running. That's what's expected to happen in the case of an out-of-date swapchain.

And, in fact, the code below it even continues and handles the error as-if the assert wasn't there, so why even bother asserting?

Anvil has this problem literally everywhere, and fixing it would require a thorough review of all the places where asserts happen, properly separating the can never fail from the can fail and we'll handle it.

Feature Request - Use Named Parameter Idiom

Vulkan (and Anvil as a result) often have functions that require A LOT of parameters, some of which tend to be optional.

Since Vulkan uses a C API, it is very clear what each argument means (albeit overly verbose):

createParams.width = 1024;
createParams.height = 1024;
createParams.depth = 1;
createParams.numLayers = 1;
createParams.mipLevels = 1;
result = vkCreateThing(&createParams);

However, Anvil's wrappers lose this property.

// Gross, lots of magic numbers
Anvil::Thing::create(1024, 1024, 1, 1, 1); 

// Much-less gross, but comments can and will get messed up as devs make changes
Anvil::Thing::create(
    1024, // width
    1024,  // height
    1, // depth
    1, // numLayers
    1 // mipLevels
    ); 

By contrast, NVidia's Vulkan C++ wrappers already use the so-called Named Parameter Idiom, which would result in code that looks like this:

auto createParams = Anvil::Thing::CreateParams().width(1024).height(1024).depth(1)
    .numLayers(1).mipLevels(1);
result = Anvil::Thing::Create(createParams);

It would be nice to see Anvil take this approach as well, but of course it's a nice-to-have and not necessarily a deal breaker :)

Inconsistency between RenderPassInfo::add_subpass_color_attachment and RenderPassInfo::add_subpass_depth_stencil_attachment

This is really just a minor thing barely worth mentioning, but the order of parameters between these methods is inconsistent:

bool add_subpass_color_attachment(SubPassID                     in_subpass_id,
                                  VkImageLayout                 in_layout,
                                  RenderPassAttachmentID        in_attachment_id,
                                  uint32_t                      in_location,
                                  const RenderPassAttachmentID* in_opt_attachment_resolve_id_ptr);
bool add_subpass_depth_stencil_attachment(SubPassID              in_subpass_id,
                                          RenderPassAttachmentID in_attachment_id,
                                          VkImageLayout          in_layout);
bool add_subpass_input_attachment(SubPassID              in_subpass_id,
                                  VkImageLayout          in_layout,
                                  RenderPassAttachmentID in_attachment_id,
                                  uint32_t               in_attachment_index);

For add_subpass_color_attachment and add_subpass_input_attachment the parameter order is SubPassID, VkImageLayout, RenderPassAttachmentID, but for add_subpass_depth_stencil_attachment the second and third parameters are swapped.

Don't ship vulkan.h in the source code

Ouch - got burned by this when I couldn't figure out why my code wouldn't compile after installing the latest Vulkan SDK on my machine, which has new structures and enums I need.

vulkan.h is checked in under Anvil/include/vulkan, and Anvil prefers to use that instead of the one from the Vulkan SDK on the machine.

IMHO Anvil should probably just use whatever SDK is installed on the machine. If there isn't one, having CMake give the old "Computer says no" seems like the right thing to do ;)

doesn't compile with gcc6/g++6

There are warnings with g++6 compiler on Linux and as -Werror is activated, it doesn't compile.

I had to add both -Wno-misleading-indentation and -Wno-strict-aliasing in order to silence those warnings and successfully compile.

GLSLShaderToSPIRVGenerator::get_spirv_blob throws std::out_of_range exception if shader compilation or linking failed

According to the description of get_shader_info_log and get_program_info_log, you should be able to call get_spirv_blob and check if its return value is nullptr to find out whether there was a problem during compilation or linking.
However looking at the definition of get_spirv_blob, it looks like it can never return nullptr and will throw an exception instead (or an assertion failure in debug mode):

const char* get_spirv_blob() const
{
    if (m_spirv_blob.size() == 0)
    {
        bool result = bake_spirv_blob();

        ANVIL_REDUNDANT_VARIABLE(result);

        anvil_assert(result);
        anvil_assert(m_spirv_blob.size() != 0);
    }

    return &m_spirv_blob.at(0);
}

As far as I can tell, there's no way to actually check if an error has occurred at the moment, even get_spirv_blob_size would cause an assertion failure.

BasePipelineManager::Pipeline::release_vulkan_objects releases layout when shouldn't?

/*

  • The function does NOT release the layout object, since it's owned by the Pipeline Layout manager.
    */
    void Anvil::BasePipelineManager::Pipeline::release_vulkan_objects()

The comment say it doesn't release the layout but the code actually does. In my tests the code causes a crash, so I think the comment is right and the code wrong.

Not sure if just removing the release will cause leaks (though I've done that on my local copy at them mo.

Binding arrays very difficult to use

Anvil::DescriptorSetGroup::set_binding_array_items() expects an array of BindingElementType values.

However - Due to some unfortunate design choices, creating such an array dynamically is very difficult. Since there is no default constructor and no copy constructor, the entire array must be initialized with known values upon creation. That's obviously not possible when the count isn't known until runtime.

So far, the only workaround I've got for this is to use placement new(), but that's really quite a pain.

Suggested fix is either to enable the default constructor and copy operator (being careful of the pitfalls of doing so with inheritance), or else change set_binding_array_items() to take an array of pointers instead so the objects can be created dynamically.

Change how GraphicsPipelineInfo::add_vertex_attribute works

I feel like the way add_vertex_attribute works at the moment is very arbitrary, counter-intuitive and it actually makes certain cases impossible as far as I can tell.

Example:

struct Vertex
{
	Vector4 position;
	Vector2 uv;
};

struct VertexWeight
{
	Vector4i boneIds;
	Vector4 boneWeights;
};

pipelineInfo.add_vertex_attribute(
	0u, /* location */
	static_cast<VkFormat>(vk::Format::eR32G32B32A32Sfloat),
	0u, /* start offset */
	sizeof(Vertex), /* stride */
	static_cast<VkVertexInputRate>(vk::VertexInputRate::eVertex)
);
pipelineInfo.add_vertex_attribute(
	1u, /* location */
	static_cast<VkFormat>(vk::Format::eR32G32Sfloat),
	sizeof(Vector4), /* start offset */
	sizeof(Vertex), /* stride */
	static_cast<VkVertexInputRate>(vk::VertexInputRate::eVertex)
);

pipelineInfo.add_vertex_attribute(
	2u, /* location */
	static_cast<VkFormat>(vk::Format::eR32G32B32A32Sint),
	0u, /* start offset */
	sizeof(VertexWeight), /* stride */
	static_cast<VkVertexInputRate>(vk::VertexInputRate::eVertex)
);
pipelineInfo.add_vertex_attribute(
	3u, /* location */
	static_cast<VkFormat>(vk::Format::eR32G32B32A32Sfloat),
	sizeof(Vector4i), /* start offset */
	sizeof(VertexWeight), /* stride */
	static_cast<VkVertexInputRate>(vk::VertexInputRate::eVertex)
);

After baking that would result in 4 vertex attributes and 2 vertex bindings, which is what I want.
However, if I were to extend the Vertex-struct in the future, e.g. like so:

struct Vertex
{
	Vector4 position;
	Vector2 uv;
	Vector2 someNewProperty;
};

then I suddenly end up with 4 vertex attributes and 1 vertex binding (because Vertex and VertexWeight now have the same size/stride).
In fact, as far as I can tell this case would make it impossible to end up with 2 vertex bindings.
Even if I try using explicit binding indices like so:

pipelineInfo.add_vertex_attribute(
	0u, /* location */
	static_cast<VkFormat>(vk::Format::eR32G32B32A32Sfloat),
	0u, /* start offset */
	sizeof(Vertex), /* stride */
	static_cast<VkVertexInputRate>(vk::VertexInputRate::eVertex),
	0u /* explicit binding index */
);
pipelineInfo.add_vertex_attribute(
	1u, /* location */
	static_cast<VkFormat>(vk::Format::eR32G32Sfloat),
	sizeof(Vector4), /* start offset */
	sizeof(Vertex), /* stride */
	static_cast<VkVertexInputRate>(vk::VertexInputRate::eVertex),
	0u /* explicit binding index */
);

pipelineInfo.add_vertex_attribute(
	2u, /* location */
	static_cast<VkFormat>(vk::Format::eR32G32B32A32Sint),
	0u, /* start offset */
	sizeof(VertexWeight), /* stride */
	static_cast<VkVertexInputRate>(vk::VertexInputRate::eVertex),
	1u /* explicit binding index */
);
pipelineInfo.add_vertex_attribute(
	3u, /* location */
	static_cast<VkFormat>(vk::Format::eR32G32B32A32Sfloat),
	sizeof(Vector4), /* start offset */
	sizeof(VertexWeight), /* stride */
	static_cast<VkVertexInputRate>(vk::VertexInputRate::eVertex),
	1u /* explicit binding index */
);

I end up with 4 vertex attributes and 4 vertex bindings (Maybe I'm using explicit binding indices incorrectly?).

I don't think the stride should be used in the first place to determine the number of vertex bindings that will be created. To be honest, I prefer the original way of defining vertex attributes/bindings over how it's done in Anvil, I find the way Anvil tries to "merge" attributes and bindings misleading (maybe that's just me though).

Exception when trying to bake descriptor set with NULL bindings

Example:

auto dsgInfos = std::vector<std::unique_ptr<Anvil::DescriptorSetInfo>>{};
auto descSetInfo = Anvil::DescriptorSetInfo::create();
const auto numBindings = 5u;
for(auto i=0u;i<numBindings;++i)
{
	descSetInfo->add_binding(
		i, /* binding    */
		static_cast<VkDescriptorType>(vk::DescriptorType::eCombinedImageSampler),
		1u, /* n_elements */
		static_cast<VkShaderStageFlags>(vk::ShaderStageFlagBits::eFragment)
	);
}
dsgInfos.push_back(std::move(descSetInfo));
auto dsg = Anvil::DescriptorSetGroup::create(dev,dsgInfos,false);
auto ds = dsg->get_descriptor_set(0u);
for(auto i=0u;i<5u;++i)
{
	ds->set_binding_item(i,Anvil::DescriptorSet::CombinedImageSamplerBindingElement{
		static_cast<VkImageLayout>(vk::ImageLayout::eShaderReadOnlyOptimal),
		imgView,sampler
	});
}
drawCmd->record_bind_descriptor_sets(
	static_cast<VkPipelineBindPoint>(vk::PipelineBindPoint::eGraphics),
	GetPipelineLayout(),
	0u, /* first set */
	1u, /* set count */
	&ds,
	0u, /* dynamic offset count */
	nullptr
);

The code above works fine, both baking and binding run without issues.
However, if you change the second loop so it only binds 4 elements (i.e. one of the bindings is NULL):

for(auto i=0u;i<4u;++i)
{
	ds->set_binding_item(i,Anvil::DescriptorSet::CombinedImageSamplerBindingElement{
		static_cast<VkImageLayout>(vk::ImageLayout::eShaderReadOnlyOptimal),
		imgView,sampler
	});
}

then you get an exception during the baking process when trying to bind the descriptor set. The exception is caused in src/wrappers/descriptor_set.cpp (Anvil::DescriptorSet::bake()), in the vkUpdateDescriptorSets-call of this code block:

/* Issue the Vulkan call */
if (m_cached_ds_write_items_vk.size() > 0)
{
    std::shared_ptr<Anvil::BaseDevice> device_locked_ptr(m_device_ptr);

    lock();
    {
        vkUpdateDescriptorSets(device_locked_ptr->get_device_vk(),
                                static_cast<uint32_t>(m_cached_ds_write_items_vk.size() ),
                                &m_cached_ds_write_items_vk[0],
                                0,        /* copyCount         */
                                nullptr); /* pDescriptorCopies */
    }
    unlock();
}

I'm not actually sure if it is valid to have NULL bindings in a descriptor set when binding it, but I can't find anything in the vulkan specification that says otherwise.

Framebuffer::add_attachment compares image view height with framebuffer layersCount

For layered rendering one wants to attach a layered 2D image view to a layered framebuffer.
The base mipmap level of such an image view is W x H x 1. This dimensions is seems to be compared to W x H and layers of the framebuffer.

framebuffer.cpp:152

 if (view_size[0] < m_framebuffer_size[0] ||
     view_size[1] < m_framebuffer_size[1] ||
     view_size[2] < m_framebuffer_size[2])
 {
     /* Attachment size is wrong */
     anvil_assert_fail();

    goto end;
 }

Compilation with VS2017 produces too many warnings

Hello developers,

I'm using Visual Studio 2017 to compile Anvil and noticed that the compilation produced about 4000 warnings. Some of the warnings are for example due to warning C4571.
So in short, there are some VS 2017 compatibility problems.

In comparison to VS2015 compilation on the same computer with no warnings being produces I guess there's something wrong.

PS: Sorry for this less information. I try to find out more and I'll edit the issue but currently it's only notice.

Automatic Baking is Tricky

Just more-or-less opening this topic for possible debate - But so far I have been burned quite a few times in Anvil because I did not understand the (somewhat unexpected) things that will silently cause a bake to happen.

For example, the following code won't work, even though it looks like it should (Arguments simplified for clarity)

// Create an allocator and use it for both color and depth attachment images

pAllocator = Anvil::MemoryAllocator::create_oneshot();

pColorImage = Anvil::Image::create_nonsparse();

pAllocator->add_image_whole(pColorImage);

pColorImageView = Anvil::ImageView::create_2D(pColorImage);

pDepthImage = Anvil::Image::create_nonsparse();

pAllocator->add_image_whole(pDepthImage ); // Won't work - Creating pColorImageView silently baked this one-shot allocator

pDepthImageView = Anvil::ImageView::create_2D(pDepthImage);

I think there are a few ways to make it more clear that wrapping an Image in an ImageView will bake the allocator:

  1. Easiest perhaps would just be to require the user to call bake() explicitly. This at least provides a hint in the code that the one-shot allocator has been used. Now the code would look like this:
pAllocator = Anvil::MemoryAllocator::create_oneshot();

pColorImage = Anvil::Image::create_nonsparse();

pAllocator->add_image_whole(pColorImage);

pDepthImage = Anvil::Image::create_nonsparse();

pAllocator->add_image_whole(pDepthImage ); 

pAllocator->bake(); // Now at-least looking at the code, I may deduce that I shouldn't rearrange the code above or below this line

pColorImageView = Anvil::ImageView::create_2D(pColorImage);

pDepthImageView = Anvil::ImageView::create_2D(pDepthImage);
  1. IMHO the "gold standard" way to do it would be to treat the allocator more like a "builder" - Add the descriptions of all the images and buffers that are assigned to it into a list. Then the user has to call "bake()", which would then actually create-and-allocate the usable Image and Buffer objects and allow the user to retrieve them.

In the second example, the above code may look more like this:

pAllocator = Anvil::MemoryAllocator::create_oneshot();

colorImageId = pAllocator->add_image_nonsparse(...);
depthImageId = pAllocator->add_image_nonsparse(...);

pBakedAllocator = pAllocator->bake();

// It is now impossible for me to create the ImageViews without a BakedAllocator/ImageId pair
pColorImageView = Anvil::ImageView::create_2D(pBakedAllocator, colorImageId);
pDepthImageView = Anvil::ImageView::create_2D(pBakedAllocator, depthImageId );

I don't know if there are other ideas on how to remove some of the footguns from this process, or if it's even worthwhile to change it. The answer might very-well just be, "This is C++ - Learn to RTFM".

Swapchain and RenderSurface should provide methods to get their current extents

Currently in Anvil, the Anvil::Swapchain doesn't record the width and height it was created with, and the Anvil::RenderSurface caches the values it was created with, but they can change when the window is resized, and it provides no way to determine the current dimensions.

This is a problem, because detecting whether the swapchain needs to be resized is a matter of getting the current extent of the rendering surface and comparing it to the extent of the swapchain.

Reimplement Windows Message Pump

Currently, Anvil's main loop uses the Windows command GetMessage() to dispatch messages to the window procedure, and intercepts the WM_PAINT message to draw.

This is not the intended use of WM_PAINT, which is meant to be used for the Windows GDI with the ValidateRect() and InvalidateRect() calls to update the client's area back when Windows was still using a stacking window manager.

The only reason this works properly right now is because Anvil does not properly handle the WM_PAINT call, which forces Windows to send it to the app window over-and-over again in hopes that the app will eventually paint its window properly.

Anvil's loop should likely switch to a message pump that looks kind-of like this (Except I would put the check for WM_QUIT in the while(PeekMessage(...)) loop)

And WM_PAINT should probably just be ignored and passed off to the default window procedure (which will just validate the entire client area, stopping future WM_PAINT messages)

Example can't find -lvulkan

Hi,

In the CMakeLists of the examples, on linux build you define the path to Vulkan_SDK as follow :

    include_directories($ENV{VK_SDK_PATH}/x86_64/include
                        $ENV{VULKAN_SDK}/x86_64/include)
    link_directories   ($ENV{VK_SDK_PATH}/x86_64/lib
                        $ENV{VULKAN_SDK}/x86_64/lib)

Or, the official LunarG pre-compiled VulkanSDK define the environment variable as follow :
export VULKAN_SDK=$PWD/x86_64
see : https://vulkan.lunarg.com/doc/sdk/1.0.42.1/linux/getting_started.html

As a result, the linker can't find it.

edit : i agree this a minor issue, but still. I think the correct behaviour should expect the environment variable defined by LunarG SDK or provide a FindVulkan.cmake

Validation error in example programs

When running one of the programs in the "examples" directory (I tried "DynamicBuffers" and "MultiViewport") with ENABLE_VALIDATION defined, the programm will run, but also causes the following validation error:

[!] Object: 0x0 | vkCreateDescriptorPool: parameter pCreateInfo->poolSizeCount must be greater than 0. The spec valid usage text states 'poolSizeCount must be greater than 0' (https://www.khronos.org/registry/vulkan/specs/1.0/html/vkspec.html#VUID-VkDescriptorPoolCreateInfo-poolSizeCount-arraylength)

Inconsistency between CommandBufferBase::record_bind_vertex_buffers and CommandBufferBase::record_bind_descriptor_sets

record_bind_descriptor_sets takes a const pointer of descriptor sets as argument:

bool record_bind_descriptor_sets(VkPipelineBindPoint                in_pipeline_bind_point,
                                 Anvil::PipelineLayout*             in_layout_ptr,
                                 uint32_t                           in_first_set,
                                 uint32_t                           in_set_count,
                                 const Anvil::DescriptorSet* const* in_descriptor_set_ptrs,
                                 uint32_t                           in_dynamic_offset_count,
                                 const uint32_t*                    in_dynamic_offset_ptrs);

record_bind_vertex_buffers on the other hand takes a non-const pointer of buffers:

bool record_bind_vertex_buffers(uint32_t            in_start_binding,
                                uint32_t            in_binding_count,
                                Anvil::Buffer**     in_buffer_ptrs,
                                const VkDeviceSize* in_offset_ptrs);

Looking at their respective implementation, I don't really see much of a reason for using a non-const pointer, so it should probably be changed to const.

Optimization - More than 1 fence not needed for image acquire

Currently (after the latest update), Anvil allocates N fences for N swapchain images.

There is no need for multiple fences - Just a single fence that you pass to the vkAcquireNextImageKHR() function and wait on immediately after. As soon as the wait is over, the fence has served its purpose and is ready to be re-used for the next call to vkAcquireNextImageKHR().

Redesign Subpass/Pipeline Distinction

It seems from the API that Anvil has a few strange interactions between Subpasses and Pipelines.

For example, the Anvil::RenderPass::add_subpass() method takes pointers to all the shader stages, even though this is a property of a Pipeline. If you don't pass in a PipelineID, it will create one for you with these shader stages, but if not then it will ignore these arguments.

The subpass also stores a pointer to a single pipeline that can be set and retrieved with get_subpass_graphics_pipeline_id() and set_subpass_graphics_pipeline_id(). But a subpass can and often will have more than one pipeline associated with it, since this is how different materials are usually handled. So I don't know that it makes sense for there to be a 1-to-1 mapping like this.

It seems like there may also be a circular dependency in the API - If I try to create a pipeline through the GraphicsPipelineManager, it requires the ID of the subpass I want to create it for.

BUT if I want to create a Subpass via RenderPass::add_subpass(), it requires a PipelineID if I don't want to just create the default.

So it seems like the only choice the API gives is to use the default pipeline that is created automatically by add_subpass()

I think probably the best course of action overall is to just leave it to the user of the API to create their own pipelines manually. Even when I was first using the API, it seemed like somewhat of an counter-intuitive side-effect that the add_subpass() method went into the GraphicsPipelineManager and added a GraphicsPipeline on my behalf.

Null Pointer Check Misses Null Pointer

From queue.cpp, line #233:

    if (in_swapchain_ptr != nullptr)
    {
    ...
    }
    swapchains_vk[0] = in_swapchain_ptr->get_swapchain_vk();

in_swapchain_ptr is used regardless of whether the null check passes or not.

Where's the example?

The readme mentions an example but it is not in this repository. Can you check it in please?

Unexpected - Sample Mask always on even when not used

Hey Dominik,

Ran into a "Oh dear - didn't expect that" situation.

The "sample mask" for MSAA is always-on, even if it's not wanted (for example, when 0 is written to it). This resulted in me ending up with a blank screen when I enabled MSAA because every single sample was masked out.

I believe the culprit is in graphics_pipeline_manager.cpp, line #636:
multisample_state_create_info.pSampleMask = &current_pipeline_config_ptr->sample_mask;

According to Vulkan Spec, this needs to be "NULL" if you're not going to use a sample mask.

To be inline with the rest of the pipeline manager features, probably the right solution is to create a method to enable/disable the Sample Mask (disabled by default), and set this to NULL if it's disabled.

Images created with mipmap data need VK_IMAGE_USAGE_TRANSFER_DST_BIT applied

I was using Anvil::Image::create_nonsparse() to create an image with mipmap data already assigned, and got back an error from validation layers that I was trying to write to an image that doesn't have VK_IMAGE_USAGE_TRANSFER_DST_BIT assigned to it.

I worked around it by adding it manually, but it doesn't say anywhere in the Anvil docs this is necessary. Anvil should probably either OR the flag in itself, or else tell the user they're required to.

Compile warnings when building without window system support

If both CMake options ANVIL_INCLUDE_WIN3264_WINDOW_SYSTEM_SUPPORT and ANVIL_INCLUDE_XCB_WINDOW_SYSTEM_SUPPORT are set to OFF the build on (Visual Studio 2017 Win64) fails with a warning that the switch in RenderingSurface::init has no "case"es.

If the above warning is fixed, the goto in the switch default creates "unreachable code" warnings.

I am interested in rendering off screen on a server. Is it not correct to set both CMake variables to OFF?

Use std::string Across the Board in API

This is mostly good, but there are a couple of spots where I am still forced to use the ".c_str()" function to pass a C-style string to Anvil.

One such example is Anvil::ShaderModule::set_name(), but there are others.

Just documenting this for the future as part of generic API cleanup.

Multiple compile failures on 32-bit Windows

On 32-bit windows, attempting to compile Anvil results in errors that look like this:

12>C:\dev\Anvil\src\wrappers\buffer.cpp(394): error C2664: 'void Anvil::DebugMarkerSupportProvider<Anvil::Buffer>::set_vk_handle(void *)': cannot convert argument 1 from 'VkBuffer' to 'void *'
12>C:\dev\Anvil\src\wrappers\buffer.cpp(394): note: Conversion from integral type to pointer type requires reinterpret_cast, C-style cast or function-style cast
12>fence.cpp
12>framebuffer.cpp
12>graphics_pipeline_manager.cpp
12>C:\dev\Anvil\src\wrappers\buffer_view.cpp(66): error C2664: 'void Anvil::DebugMarkerSupportProvider<Anvil::BufferView>::set_vk_handle(void *)': cannot convert argument 1 from 'VkBufferView' to 'void *'
12>C:\dev\Anvil\src\wrappers\buffer_view.cpp(66): note: Conversion from integral type to pointer type requires reinterpret_cast, C-style cast or function-style cast
12>image.cpp
12>C:\dev\Anvil\src\wrappers\command_pool.cpp(71): error C2664: 'void Anvil::DebugMarkerSupportProvider<Anvil::CommandPool>::set_vk_handle(void *)': cannot convert argument 1 from 'VkCommandPool' to 'void *'
12>C:\dev\Anvil\src\wrappers\command_pool.cpp(71): note: Conversion from integral type to pointer type requires reinterpret_cast, C-style cast or function-style cast
12>C:\dev\Anvil\src\wrappers\descriptor_pool.cpp(210): error C2664: 'void Anvil::DebugMarkerSupportProvider<Anvil::DescriptorPool>::set_vk_handle(void *)': cannot convert argument 1 from 'VkDescriptorPool' to 'void *'
12>C:\dev\Anvil\src\wrappers\descriptor_pool.cpp(210): note: Conversion from integral type to pointer type requires reinterpret_cast, C-style cast or function-style cast

Upon further investigation, it appears that the issue is that all of these failures try to cast the type defined by the VK_DEFINE_NON_DISPATCHABLE_HANDLE macro to a void *. Here is the definition for that macro:

#if !defined(VK_DEFINE_NON_DISPATCHABLE_HANDLE)
#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__) ) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__)
        #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object;
#else
        #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;
#endif
#endif

This works fine on 64-bit systems because any pointer can be cast to a void *, but on 32-bit systems obviously a 64-bit uint can't generally be casted to a void *.

Anvil Examples have Synchronization Issues

Some of Anvil's demos have synchronization issues. I personally used the Push Constants one, but from what I can tell they all have similar problems.

  1. The "Push Constants" example uses a Uniform Buffer - But it only has 1 of them that's shared by all the command buffers. Since the GPU may be in the middle of processing the previous frame when the CPU updates that UB, this creates a race condition between the GPU and the CPU. There likely needs to be a UB for every command buffer

  2. No CPU fence - The part of the example that is responsible for submitting the command buffers to the Graphics and Present queues doesn't wait on any fences. This means that the CPU never synchronizes with the other engines and could just keep cranking out frame-after-frame-after-frame without stopping until it runs out of memory if the GPU and/or presentation engine can't keep up.

    The reason that it works right now is that I'm fairly certain that the vkAcquireNextImageKHR currently does block to wait until the next image is available, although it explicitly says in the Vulkan Spec not to count on this behavior.

Allow usage of existing window

It would be nice to have a way to create an Anvil::Window from an existing WindowHandle/XCB connection.

That way one could use Anvil::Swapchain with an SDL window, which is not possible currently (as far as I can see?).

examples/OutOfOrderRasterization: Maximizing or resizing the window results in SIGSEGV

(gdb) bt
#0  0x000000000049ba3b in App::draw_frame (app_raw_ptr=0x1f40b30)
    at Anvil/examples/OutOfOrderRasterization/src/app.cpp:299
#1  0x000000000051400e in Anvil::WindowXcb::run (this=0x2141140)
    at Anvil/src/misc/window_xcb.cpp:253
#2  0x0000000000493e9e in App::run (this=this@entry=0x1f40b30)
    at Anvil/examples/OutOfOrderRasterization/src/app.cpp:1082
#3  0x000000000049e572 in main () at Anvil/examples/OutOfOrderRasterization/src/app.cpp:1180

Don't have a `destroy()` method on Device, PhysicalDevice, Instance

I'm not really sure what the purpose is of having a separate destroy() function on any of these 3 classes.

What is the meaning of an object that's "destroyed, but still exists"? What would a user do with such an object?

Unless I'm missing something, it's probably best to move all that logic to the objects' dtors and remove the destroy() method. That way RAII can do its job and clean up properly, and you're never left with an object that's still-hanging-around but not useful for anything.

Potential CB Record without 'start_recording()' call

I'm not really sure what the correct behavior should be here (perhaps just an assert), but buffer.cpp has this code starting on line 719:

        if (device_type == Anvil::DEVICE_TYPE_SINGLE_GPU)
        {
            copy_cmdbuf_ptr->start_recording(true,   /* one_time_submit          */
                                             false); /* simultaneous_use_allowed */
        }
        {
            VkBufferCopy copy_region;

            copy_region.dstOffset = in_start_offset;
            copy_region.size      = in_size;
            copy_region.srcOffset = 0;

            copy_cmdbuf_ptr->record_copy_buffer(staging_buffer_ptr,
                                                shared_from_this(),
                                                1, /* in_region_count */
                                               &copy_region);
        }
        copy_cmdbuf_ptr->stop_recording();

        if (device_type == Anvil::DEVICE_TYPE_SINGLE_GPU)
        {
            queue_ptr->submit_command_buffer(copy_cmdbuf_ptr,
                                             true /* should_block */);
        }

It looks like if that first "if" statement is false, the command buffer will be written to and stop_recording() will be called without start_recording() even having been called.

This function should probably be changed to either handle other device types, or else just assert that it can't. I'm not really sure what the intended scope is with this.

Update readme.md to More Properly Reflect Reality of using Anvil

In the readme.md file shipped with Anvil, the following claim is made:

You are free to use any of its parts you like, and write your own code for everything else. 
Any Anvil wrapper for an actual Vulkan object can be queried to retrieve the raw 
Vulkan handle.

I think that this claim is somewhat misleading for a developer that is window-shopping for a library or framework to use in their code. The following 2 points make it very difficult to mix raw Vulkan objects with Anvil objects:

  1. The Anvil classes are mostly designed to be used only with other Anvil classes, and don't take raw Vulkan handles as arguments for almost any of their functionality.
  2. If the user uses the raw Vulkan handles returned by Anvil, they risk causing the Vulkan state to go out-of-sync with the Anvil state, leading to all kinds of undefined behavior

So creating your own Vulkan objects and trying to use them in Anvil is not possible, and having Anvil create your objects and then trying to use them with the Vulkan API is often dangerous.

I feel that this part of the readme should be updated to indicate that if somebody decides to use Anvil, they're all-in for strictly using Anvil - There doesn't really exist some half-measure where they can dip their toe in the water, or workaround missing functionality.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.