Skip to content

Commit

Permalink
[gpu.vulkan] new buffer system. how it works is all resource buffers …
Browse files Browse the repository at this point in the history
…are infact not descriptors, but are BDA-ready. every buffer's address is placed (in order) into a secret meta-buffer which is always binding 0 in all shaders (this is abstracted away in TZSL). now, resource(id = X) const buffer now compile down to GLSL GL_EXT_buffer_reference definitions, and the accumulation of them all defines the meta-buffer shader side (containing simply references to all the buffer resources in order). this does have one drawback: if you have multiple buffer resources, but do not define them all in the shader, then the buffer-references will point to the wrong thing and cause bugs. so there's a new rule: in TZSL shaders if you use a buffer resource in a particular shader, you *must* declare them ALL.
  • Loading branch information
harrand committed Oct 17, 2024
1 parent 3559829 commit 308cec2
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 21 deletions.
11 changes: 10 additions & 1 deletion demo/tz/shaders/triangle.vertex.tzsl
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,18 @@ vec3 colours[3] = vec3[]
);

output(id = 0) vec3 frag_colour;
resource(id = 0) const buffer meta_buffer
{
uint x;
};

resource(id = 1) const buffer not_a_buffer
{
uint x;
};

void main()
{
out::position = vec4(positions[in::vertex_id], 0.0, 1.0);
frag_colour = colours[in::vertex_id];
frag_colour = colours[meta_buffer.x];
}
10 changes: 10 additions & 0 deletions demo/tz/tz_triangle_demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ int main()
tz::gpu::window_resource,
};

int x = 2;
tz::gpu::resource_handle my_buffer = tz_must(tz::gpu::create_buffer
({
.access = tz::gpu::resource_access::static_access,
.type = tz::gpu::buffer_type::storage,
.data = std::as_bytes(std::span<const int>{&x, 1}),
.name = "my buffer resource :)"
}));

tz::gpu::pass_handle pass = tz_must(tz::gpu::create_pass
({
.graphics =
Expand All @@ -32,6 +41,7 @@ int main()
.depth_target = tz::gpu::window_resource
},
.shader = graphics,
.resources = {&my_buffer, 1}
}));

tz::gpu::graph_handle graph = tz_must(tz::gpu::graph_builder{}
Expand Down
135 changes: 118 additions & 17 deletions src/tz/gpu/rhi_vulkan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ namespace tz::gpu
pass_info info;
VkPipelineLayout layout = VK_NULL_HANDLE;
VkPipeline pipeline = VK_NULL_HANDLE;
VkBuffer meta_buffer = VK_NULL_HANDLE;
VmaAllocation meta_buffer_mem = VK_NULL_HANDLE;
std::array<VkDescriptorSet, frame_overlap> descriptor_sets = {};
std::uint32_t viewport_width = 0;
std::uint32_t viewport_height = 0;
Expand Down Expand Up @@ -1005,11 +1007,40 @@ namespace tz::gpu
UNERR(tz::error_code::precondition_failure, "no shader program provided when creating pass. you must provide a valid shader program.");
}
std::size_t ret_id = passes.size();
auto& pass = passes.emplace_back();
std::size_t buffer_count = 0;
for(resource_handle resh : info.resources)
{
// todo: assert not widnow resouces or nullhand
const auto& res = resources[resh.peek()];
if(res.is_buffer())
{
buffer_count++;
}
}
// the meta buffer must always exist. if we have no buffer resources, then it just sits at a single byte.
VkBufferCreateInfo meta_create
{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.size = buffer_count > 0 ? sizeof(VkDeviceAddress) * buffer_count : 1,
.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 1,
.pQueueFamilyIndices = &current_hardware.internals.i1,
};
VmaAllocationCreateInfo meta_alloc_create
{
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE
};
VkResult meta_result = vmaCreateBuffer(alloc, &meta_create, &meta_alloc_create, &pass.meta_buffer, &pass.meta_buffer_mem, nullptr);
tz_assert(meta_result == VK_SUCCESS, "internal error - failed to create meta buffer.");

auto top_part = (info.shader.peek() >> 16) & 0xFFFFFFFF;
auto& shader1 = shaders[--top_part];
auto bottom_part = info.shader.peek() & 0x0000FFFF;

auto& pass = passes.emplace_back();
VkResult res = VK_SUCCESS;

if(shader1.ty == shader_type::compute)
Expand Down Expand Up @@ -1291,6 +1322,7 @@ namespace tz::gpu
auto i = pass.peek();
tz_assert(passes.size() > i, "Dodgy handle (value {}) passed to destroy_pass", i);
vkDestroyPipeline(current_device, passes[i].pipeline, nullptr);
vmaDestroyBuffer(alloc, passes[i].meta_buffer, passes[i].meta_buffer_mem);
passes[i] = {};
}

Expand Down Expand Up @@ -2010,13 +2042,15 @@ namespace tz::gpu
{
return;
}
std::vector<VkDeviceAddress> buffer_addresses = {};
for(const auto& resh : pass.info.resources)
{
const auto& res = resources[resh.peek()];
if(res.is_buffer())
{
const auto& buffer = std::get<buffer_info>(res.res);
impl_cmd_resource_write(scratch_cmds, resh, buffer.data, 0);
buffer_addresses.push_back(res.buffer_device_address);
}
else if(res.is_image())
{
Expand All @@ -2029,6 +2063,39 @@ namespace tz::gpu
}
}

if(buffer_addresses.size())
{
tz_assert(pass.meta_buffer != VK_NULL_HANDLE, "meta buffer wasnt detected when it should exist (coz we have {} buffers)", buffer_addresses.size());
scratchbuf_t& scratchbuf = scratch_buffers.emplace_back();
std::size_t meta_buffer_size = buffer_addresses.size() * sizeof(buffer_addresses[0]);
VkBufferCreateInfo create
{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.size = meta_buffer_size,
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 1,
.pQueueFamilyIndices = &current_hardware.internals.i1
};
VmaAllocationCreateInfo alloc_create
{
.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT,
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST,
};
VmaAllocationInfo alloc_out;
vmaCreateBuffer(alloc, &create, &alloc_create, &scratchbuf.buf, &scratchbuf.mem, &alloc_out);
std::memcpy(alloc_out.pMappedData, buffer_addresses.data(), meta_buffer_size);
VkBufferCopy cpy
{
.srcOffset = 0,
.dstOffset = 0,
.size = meta_buffer_size
};
vkCmdCopyBuffer(scratch_cmds, scratchbuf.buf, pass.meta_buffer, 1, &cpy);
}

vkEndCommandBuffer(scratch_cmds);
VkPipelineStageFlags stage_mask = VK_PIPELINE_STAGE_NONE;
VkSubmitInfo submit
Expand Down Expand Up @@ -2399,10 +2466,34 @@ namespace tz::gpu
impl_new_pool();
pool = descriptor_pools.back();
}
constexpr std::uint32_t image_array_descriptor_binding = 0;
constexpr std::uint32_t image_array_descriptor_binding = 1;
std::vector<VkDescriptorImageInfo> image_writes;
std::array<VkWriteDescriptorSet, frame_overlap> descriptor_writes;
image_writes.reserve(pass.info.resources.size());
VkDescriptorBufferInfo meta_buffer_write
{
.buffer = pass.meta_buffer,
.offset = 0,
.range = VK_WHOLE_SIZE
};
image_writes.reserve(pass.info.resources.size() * frame_overlap);
std::vector<VkWriteDescriptorSet> descriptor_writes;
// first do meta buffers so they're first in the list.
for(std::size_t j = 0; j < frame_overlap; j++)
{
descriptor_writes.push_back(VkWriteDescriptorSet
{
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr,
.dstSet = pass.descriptor_sets[j],
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.pImageInfo = nullptr,
.pBufferInfo = &meta_buffer_write,
.pTexelBufferView = nullptr
});
}

std::size_t image_count = 0;
for(std::size_t j = 0; j < frame_overlap; j++)
{
Expand All @@ -2424,7 +2515,11 @@ namespace tz::gpu
}
}
}
descriptor_writes[j] = VkWriteDescriptorSet
if(image_count == 0)
{
break;
}
descriptor_writes.push_back(VkWriteDescriptorSet
{
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.pNext = nullptr,
Expand All @@ -2436,27 +2531,33 @@ namespace tz::gpu
.pImageInfo = image_writes.data() + (j * image_count),
.pBufferInfo = nullptr,
.pTexelBufferView = nullptr
};
}
if(image_count == 0)
{
// no need to update anything if tere's no images at all.
return;
});
}
vkUpdateDescriptorSets(current_device, descriptor_writes.size(), descriptor_writes.data(), 0, nullptr);
}

VkPipelineLayout impl_create_layout()
{
VkDescriptorSetLayoutBinding images_binding
std::array<VkDescriptorSetLayoutBinding, 2> bindings{};
bindings[0] =
{
.binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_ALL,
.pImmutableSamplers = nullptr
};
bindings[1] =
{
.binding = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.descriptorCount = max_global_image_count,
.stageFlags = VK_SHADER_STAGE_ALL,
.pImmutableSamplers = nullptr
};
VkDescriptorBindingFlags flags =
std::array<VkDescriptorBindingFlags, 2> flags{};
flags[0] = 0;
flags[1] =
VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT |
VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT |
VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT |
Expand All @@ -2466,17 +2567,17 @@ namespace tz::gpu
{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO,
.pNext = nullptr,
.bindingCount = 1,
.pBindingFlags = &flags
.bindingCount = flags.size(),
.pBindingFlags = flags.data()
};
VkDescriptorSetLayoutCreateInfo dlcreate
{
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.pNext = &flags_create,
.flags =
VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT,
.bindingCount = 1,
.pBindings = &images_binding
.bindingCount = bindings.size(),
.pBindings = bindings.data()
};
for(std::size_t i = 0; i < frame_overlap; i++)
{
Expand Down
40 changes: 37 additions & 3 deletions tools/tzslc/tzsl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "source_transform.hpp"
#include <fstream>
#include <sstream>
#include <map>

#include "stdlib.hpp"

Expand All @@ -24,8 +25,11 @@ namespace tzslc
void evaluate_user_imports(std::string&, std::filesystem::path);
void evaluate_keywords(std::string&, ShaderStage, GLSLDialect);
void evaluate_inout_blocks(std::string&, ShaderStage, GLSLDialect);
void evaluate_main_function(std::string&);
void collapse_namespaces(std::string&);

std::map<int, std::string> buffer_id_to_BDA_typename;

//--------------------------------------------------------------------------------------------------

void compile_to_glsl(std::string& shader_source, std::filesystem::path shader_filename, GLSLDialect dialect, BuildConfig build_config)
Expand All @@ -39,6 +43,8 @@ namespace tzslc
evaluate_keywords(shader_source, stage, dialect);
evaluate_inout_blocks(shader_source, stage, dialect);
collapse_namespaces(shader_source);
evaluate_main_function(shader_source);
buffer_id_to_BDA_typename.clear();
}

//--------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -89,7 +95,7 @@ namespace tzslc
std::string ret = "/*tzslc header info*/\n#version 460 core\n";
if(dialect == GLSLDialect::Vulkan)
{
ret += "#define TZ_OGL 0\n#define TZ_VULKAN 1\n#extension GL_EXT_debug_printf : enable\n#extension GL_EXT_nonuniform_qualifier : enable\n";
ret += "#define TZ_OGL 0\n#define TZ_VULKAN 1\n#extension GL_EXT_debug_printf : enable\n#extension GL_EXT_nonuniform_qualifier : enable\n#extension GL_EXT_buffer_reference : require\n";
}
else if(dialect == GLSLDialect::OpenGL)
{
Expand Down Expand Up @@ -299,17 +305,22 @@ namespace tzslc

// Finally, resources.
// Start with buffer resources.
constexpr char buffer_resource_regex[] = "resource\\(id ?= ?([0-9]+)\\) ?([a-zA-Z]*) ?buffer";
constexpr char buffer_resource_regex[] = "resource\\(id ?= ?([0-9]+)\\) ?([a-zA-Z]*) ?buffer ([a-zA-Z_]+)";
tzslc::transform(shader_source, std::regex{buffer_resource_regex},
[](auto beg, auto end)
{
std::string id = *(beg);
auto id_val = std::stoi(id);
std::string flag = *(beg + 1);
std::string bda_name = *(beg + 2);
tzslc_assert(flag.empty() || (flag == "const"), "Detected unrecognised token `%s` in buffer resource specifier. Replace with nothing, or `const`.", flag.c_str());
if(flag == "const")
{
flag = "readonly";
}
return "layout(binding = " + *(beg) + ") " + flag + " buffer";
buffer_id_to_BDA_typename[id_val] = bda_name;
return "layout(std430, buffer_reference, buffer_reference_align = 8) " + flag + " buffer " + bda_name + "_t";
//return "layout(binding = " + id + ") " + flag + " buffer " + bda_name;
});

// And then texture resources.
Expand Down Expand Up @@ -442,6 +453,29 @@ namespace tzslc
}
}

//--------------------------------------------------------------------------------------------------

void evaluate_main_function(std::string& shader_source)
{
if(buffer_id_to_BDA_typename.empty())
{
return;
}
tzslc::transform(shader_source, std::regex{"void main"},
[](auto beg, auto end)
{
// sneak in our magic BDA meta buffer.
std::string prefix = "";
prefix = "layout(std430, set = 0, binding = 0) readonly buffer MetaBuffer{\n";
for(const auto& [id, bda_name] : buffer_id_to_BDA_typename)
{
prefix += bda_name + "_t " + bda_name + ";\n";
}
prefix += "};";
return prefix + "\nvoid main";
});
}

//--------------------------------------------------------------------------------------------------

void collapse_namespaces(std::string& shader_source)
Expand Down

0 comments on commit 308cec2

Please sign in to comment.