constexpr std::uint32_t vertex_end_index = 3;
constexpr std::uint32_t instance_id = 0;
constexpr std::size_t vertex_count = vertex_end_index - vertex_start_index;
- std::unique_ptr<unsigned char[]> output_buffer(
- new unsigned char[graphics_pipeline->get_vertex_shader_output_struct_size()
- * vertex_count]);
- auto vertex_shader_function = graphics_pipeline->get_vertex_shader_function();
- vertex_shader_function(
+ std::size_t output_buffer_size =
+ graphics_pipeline->get_vertex_shader_output_struct_size() * vertex_count;
+ std::unique_ptr<unsigned char[]> output_buffer(new unsigned char[output_buffer_size]);
+ for(std::size_t i = 0; i < output_buffer_size; i++)
+ output_buffer[i] = 0;
+ graphics_pipeline->run_vertex_shader(
vertex_start_index, vertex_end_index, instance_id, output_buffer.get());
std::cerr << "shader completed" << std::endl;
+ for(std::size_t i = 0; i < vertex_count; i++)
+ {
+ graphics_pipeline->dump_vertex_shader_output_struct(output_buffer.get()
+ + graphics_pipeline->get_vertex_shader_output_struct_size() * i);
+ }
}
catch(std::runtime_error &e)
{
#
cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
-set(sources llvm_wrapper.cpp)
+set(sources llvm_wrapper.cpp
+ orc_compile_stack.cpp)
add_library(vulkan_cpu_llvm_wrapper STATIC ${sources})
if(0)
llvm_map_components_to_libnames(llvm_libraries core native analysis orcjit mcjit nativecodegen)
struct LLVM_string_deleter
{
- void operator()(char *str)
+ void operator()(char *str) const noexcept
{
::LLVMDisposeMessage(str);
}
struct Context_deleter
{
- void operator()(::LLVMContextRef context) noexcept
+ void operator()(::LLVMContextRef context) const noexcept
{
::LLVMContextDispose(context);
}
struct Target_deleter
{
- void operator()(::LLVMTargetRef target) noexcept
+ void operator()(::LLVMTargetRef target) const noexcept
{
static_cast<void>(target);
}
struct Target_data_deleter
{
- void operator()(::LLVMTargetDataRef v) noexcept
+ void operator()(::LLVMTargetDataRef v) const noexcept
{
::LLVMDisposeTargetData(v);
}
struct Target_machine_deleter
{
- void operator()(::LLVMTargetMachineRef tm) noexcept
+ void operator()(::LLVMTargetMachineRef tm) const noexcept
{
::LLVMDisposeTargetMachine(tm);
}
struct Module_deleter
{
- void operator()(::LLVMModuleRef module) noexcept
+ void operator()(::LLVMModuleRef module) const noexcept
{
::LLVMDisposeModule(module);
}
struct Builder_deleter
{
- void operator()(::LLVMBuilderRef v) noexcept
+ void operator()(::LLVMBuilderRef v) const noexcept
{
return ::LLVMDisposeBuilder(v);
}
#if LLVM_WRAPPER_ORC_REVISION_NUMBER >= 306166
struct Orc_shared_module_ref_deleter
{
- void operator()(::LLVMSharedModuleRef v) noexcept
+ void operator()(::LLVMSharedModuleRef v) const noexcept
{
::LLVMOrcDisposeSharedModuleRef(v);
}
struct Orc_jit_stack_deleter
{
- void operator()(::LLVMOrcJITStackRef v) noexcept
+ void operator()(::LLVMOrcJITStackRef v) const noexcept
{
::LLVMOrcDisposeInstance(v);
}
--- /dev/null
+/*
+ * Copyright 2017 Jacob Lifshay
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+#include "orc_compile_stack.h"
+#include <llvm/ExecutionEngine/ExecutionEngine.h>
+#include <llvm/ExecutionEngine/RTDyldMemoryManager.h>
+#include <llvm/ExecutionEngine/JITEventListener.h>
+#include <llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h>
+#include <llvm/Target/TargetMachine.h>
+#include <llvm/Config/llvm-config.h>
+
+#if LLVM_VERSION_MAJOR != 4 || LLVM_VERSION_MINOR != 0
+#error Orc compile stack is not yet implemented for this version of LLVM
+#endif
+
+namespace vulkan_cpu
+{
+namespace llvm_wrapper
+{
+namespace
+{
+// implement the unwrap functions that aren't in public llvm headers
+llvm::TargetMachine *unwrap(::LLVMTargetMachineRef v) noexcept
+{
+ return reinterpret_cast<llvm::TargetMachine *>(v);
+}
+}
+
+class Orc_compile_stack_implementation
+{
+ Orc_compile_stack_implementation(const Orc_compile_stack_implementation &) = delete;
+ Orc_compile_stack_implementation(Orc_compile_stack_implementation &&) = delete;
+ Orc_compile_stack_implementation &operator=(const Orc_compile_stack_implementation &) = delete;
+ Orc_compile_stack_implementation &operator=(Orc_compile_stack_implementation &&) = delete;
+
+private:
+ std::unique_ptr<llvm::TargetMachine> target_machine;
+ const llvm::DataLayout data_layout;
+ llvm::orc::ObjectLinkingLayer<> object_linking_layer;
+
+public:
+ explicit Orc_compile_stack_implementation(Target_machine target_machine_in)
+ : target_machine(unwrap(target_machine_in.release())),
+ data_layout(target_machine->createDataLayout())
+ {
+#warning finish
+ assert(!"finish");
+ }
+ void add_eagerly_compiled_ir(Module module,
+ ::LLVMOrcSymbolResolverFn symbol_resolver_callback,
+ void *symbol_resolver_user_data)
+ {
+#warning finish
+ assert(!"finish");
+ }
+ std::uintptr_t get_symbol_address(const char *symbol_name)
+ {
+#warning finish
+ assert(!"finish");
+ return 0;
+ }
+};
+
+void Orc_compile_stack_deleter::operator()(Orc_compile_stack_ref v) const noexcept
+{
+ delete v;
+}
+
+Orc_compile_stack Orc_compile_stack::create(Target_machine target_machine)
+{
+#warning finish
+ assert(!"finish");
+ return {};
+}
+
+void Orc_compile_stack::add_eagerly_compiled_ir(Orc_compile_stack_ref orc_compile_stack,
+ Module module,
+ ::LLVMOrcSymbolResolverFn symbol_resolver_callback,
+ void *symbol_resolver_user_data)
+{
+ orc_compile_stack->add_eagerly_compiled_ir(
+ std::move(module), symbol_resolver_callback, symbol_resolver_user_data);
+}
+
+std::uintptr_t Orc_compile_stack::get_symbol_address(Orc_compile_stack_ref orc_compile_stack,
+ const char *symbol_name)
+{
+ return orc_compile_stack->get_symbol_address(symbol_name);
+}
+}
+}
--- /dev/null
+/*
+ * Copyright 2017 Jacob Lifshay
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+#ifndef LLVM_WRAPPER_ORC_COMPILE_STACK_H_
+#define LLVM_WRAPPER_ORC_COMPILE_STACK_H_
+
+#include "llvm_wrapper.h"
+
+namespace vulkan_cpu
+{
+namespace llvm_wrapper
+{
+class Orc_compile_stack_implementation;
+
+typedef Orc_compile_stack_implementation *Orc_compile_stack_ref;
+
+struct Orc_compile_stack_deleter
+{
+ void operator()(Orc_compile_stack_ref v) const noexcept;
+};
+
+struct Orc_compile_stack : public Wrapper<Orc_compile_stack_ref, Orc_compile_stack_deleter>
+{
+ using Wrapper::Wrapper;
+ static Orc_compile_stack create(Target_machine target_machine);
+ static void add_eagerly_compiled_ir(Orc_compile_stack_ref orc_compile_stack,
+ Module module,
+ ::LLVMOrcSymbolResolverFn symbol_resolver_callback,
+ void *symbol_resolver_user_data);
+ void add_eagerly_compiled_ir(Module module,
+ ::LLVMOrcSymbolResolverFn symbol_resolver_callback,
+ void *symbol_resolver_user_data)
+ {
+ add_eagerly_compiled_ir(
+ get(), std::move(module), symbol_resolver_callback, symbol_resolver_user_data);
+ }
+ static std::uintptr_t get_symbol_address(Orc_compile_stack_ref orc_compile_stack,
+ const char *symbol_name);
+ template <typename T>
+ static T *get_symbol(Orc_compile_stack_ref orc_compile_stack, const char *symbol_name)
+ {
+ return reinterpret_cast<T *>(get_symbol_address(orc_compile_stack, symbol_name));
+ }
+ std::uintptr_t get_symbol_address(const char *symbol_name)
+ {
+ return get_symbol_address(get(), symbol_name);
+ }
+ template <typename T>
+ T *get_symbol(const char *symbol_name)
+ {
+ return get_symbol<T>(get(), symbol_name);
+ }
+};
+}
+}
+
+#endif // LLVM_WRAPPER_ORC_COMPILE_STACK_H_
set(sources pipeline.cpp)
add_library(vulkan_cpu_pipeline STATIC ${sources})
-target_link_libraries(vulkan_cpu_pipeline vulkan_cpu_spirv_to_llvm vulkan_cpu_util vulkan_cpu_spirv vulkan_cpu_llvm_wrapper vulkan_cpu_vulkan)
+target_link_libraries(vulkan_cpu_pipeline vulkan_cpu_spirv_to_llvm vulkan_cpu_json vulkan_cpu_util vulkan_cpu_spirv vulkan_cpu_llvm_wrapper vulkan_cpu_vulkan)
#include "spirv_to_llvm/spirv_to_llvm.h"
#include "llvm_wrapper/llvm_wrapper.h"
#include "vulkan/util.h"
+#include "util/soft_float.h"
+#include "json/json.h"
#include <stdexcept>
#include <cassert>
#include <vector>
struct Graphics_pipeline::Implementation
{
+ llvm_wrapper::Context llvm_context = llvm_wrapper::Context::create();
spirv_to_llvm::Jit_symbol_resolver jit_symbol_resolver;
llvm_wrapper::Orc_jit_stack jit_stack;
- llvm_wrapper::Context llvm_context = llvm_wrapper::Context::create();
- std::uint64_t next_module_id = 1;
- std::uint64_t make_module_id() noexcept
+ llvm_wrapper::Target_data data_layout;
+ std::vector<spirv_to_llvm::Converted_module> compiled_shaders;
+ std::shared_ptr<spirv_to_llvm::Struct_type_descriptor> vertex_shader_output_struct;
+ std::string append_value_to_string(std::string str,
+ spirv_to_llvm::Type_descriptor &type,
+ const void *value) const
{
- return next_module_id++;
+ struct Visitor : public spirv_to_llvm::Type_descriptor::Type_visitor
+ {
+ const Implementation *this_;
+ std::string &str;
+ const void *value;
+ Visitor(const Implementation *this_, std::string &str, const void *value) noexcept
+ : this_(this_),
+ str(str),
+ value(value)
+ {
+ }
+ virtual void visit(spirv_to_llvm::Simple_type_descriptor &type) override
+ {
+ auto llvm_type = type.get_or_make_type().type;
+ switch(::LLVMGetTypeKind(llvm_type))
+ {
+ case ::LLVMVoidTypeKind:
+ case ::LLVMX86_FP80TypeKind:
+ case ::LLVMFP128TypeKind:
+ case ::LLVMPPC_FP128TypeKind:
+ case ::LLVMLabelTypeKind:
+ case ::LLVMFunctionTypeKind:
+ case ::LLVMStructTypeKind:
+ case ::LLVMArrayTypeKind:
+ case ::LLVMPointerTypeKind:
+ case ::LLVMVectorTypeKind:
+ case ::LLVMMetadataTypeKind:
+ case ::LLVMX86_MMXTypeKind:
+ case ::LLVMTokenTypeKind:
+ break;
+ case ::LLVMHalfTypeKind:
+ {
+ auto integer_value = *static_cast<const std::uint16_t *>(value);
+ auto float_value =
+ util::soft_float::ExtendedFloat::fromHalfPrecision(integer_value);
+ str = json::ast::Number_value::append_double_to_string(
+ static_cast<double>(float_value), std::move(str));
+ if(float_value.isNaN())
+ {
+ str += " (0x";
+ str = json::ast::Number_value::append_unsigned_integer_to_string(
+ integer_value, std::move(str), 0x10);
+ str += ")";
+ }
+ return;
+ }
+ case ::LLVMFloatTypeKind:
+ {
+ static_assert(sizeof(std::uint32_t) == sizeof(float)
+ && alignof(std::uint32_t) == alignof(float),
+ "");
+ union
+ {
+ std::uint32_t integer_value;
+ float float_value;
+ };
+ integer_value = *static_cast<const std::uint32_t *>(value);
+ str = json::ast::Number_value::append_double_to_string(float_value,
+ std::move(str));
+ if(std::isnan(float_value))
+ {
+ str += " (0x";
+ str = json::ast::Number_value::append_unsigned_integer_to_string(
+ integer_value, std::move(str), 0x10);
+ str += ")";
+ }
+ return;
+ }
+ case ::LLVMDoubleTypeKind:
+ {
+ static_assert(sizeof(std::uint64_t) == sizeof(double)
+ && alignof(std::uint64_t) == alignof(double),
+ "");
+ union
+ {
+ std::uint64_t integer_value;
+ double float_value;
+ };
+ integer_value = *static_cast<const std::uint64_t *>(value);
+ str = json::ast::Number_value::append_double_to_string(float_value,
+ std::move(str));
+ if(std::isnan(float_value))
+ {
+ str += " (0x";
+ str = json::ast::Number_value::append_unsigned_integer_to_string(
+ integer_value, std::move(str), 0x10);
+ str += ")";
+ }
+ return;
+ }
+ case ::LLVMIntegerTypeKind:
+ {
+ switch(::LLVMGetIntTypeWidth(llvm_type))
+ {
+ case 8:
+ {
+ auto integer_value = *static_cast<const std::uint8_t *>(value);
+ str += "0x";
+ str = json::ast::Number_value::append_unsigned_integer_to_string(
+ integer_value, std::move(str), 0x10);
+ str += " ";
+ str = json::ast::Number_value::append_unsigned_integer_to_string(
+ integer_value, std::move(str));
+ str += " ";
+ str = json::ast::Number_value::append_signed_integer_to_string(
+ static_cast<std::int8_t>(integer_value), std::move(str));
+ return;
+ }
+ case 16:
+ {
+ auto integer_value = *static_cast<const std::uint16_t *>(value);
+ str += "0x";
+ str = json::ast::Number_value::append_unsigned_integer_to_string(
+ integer_value, std::move(str), 0x10);
+ str += " ";
+ str = json::ast::Number_value::append_unsigned_integer_to_string(
+ integer_value, std::move(str));
+ str += " ";
+ str = json::ast::Number_value::append_signed_integer_to_string(
+ static_cast<std::int16_t>(integer_value), std::move(str));
+ return;
+ }
+ case 32:
+ {
+ auto integer_value = *static_cast<const std::uint32_t *>(value);
+ str += "0x";
+ str = json::ast::Number_value::append_unsigned_integer_to_string(
+ integer_value, std::move(str), 0x10);
+ str += " ";
+ str = json::ast::Number_value::append_unsigned_integer_to_string(
+ integer_value, std::move(str));
+ str += " ";
+ str = json::ast::Number_value::append_signed_integer_to_string(
+ static_cast<std::int32_t>(integer_value), std::move(str));
+ return;
+ }
+ case 64:
+ {
+ auto integer_value = *static_cast<const std::uint64_t *>(value);
+ str += "0x";
+ str = json::ast::Number_value::append_unsigned_integer_to_string(
+ integer_value, std::move(str), 0x10);
+ str += " ";
+ str = json::ast::Number_value::append_unsigned_integer_to_string(
+ integer_value, std::move(str));
+ str += " ";
+ str = json::ast::Number_value::append_signed_integer_to_string(
+ static_cast<std::int64_t>(integer_value), std::move(str));
+ return;
+ }
+ }
+ break;
+ }
+ }
+ assert(!"unhandled type");
+ throw std::runtime_error("unhandled type");
+ }
+ virtual void visit(spirv_to_llvm::Vector_type_descriptor &type) override
+ {
+ auto llvm_element_type = type.get_element_type()->get_or_make_type().type;
+ std::size_t element_size =
+ ::LLVMABISizeOfType(this_->data_layout.get(), llvm_element_type);
+ std::size_t element_count = type.get_element_count();
+ str += "<";
+ auto separator = "";
+ for(std::size_t i = 0; i < element_count; i++)
+ {
+ str += separator;
+ separator = ", ";
+ str = this_->append_value_to_string(
+ std::move(str),
+ *type.get_element_type(),
+ static_cast<const char *>(value) + i * element_size);
+ }
+ str += ">";
+ }
+ virtual void visit(spirv_to_llvm::Matrix_type_descriptor &type) override
+ {
+ assert(!"dumping matrix not implemented");
+ throw std::runtime_error("dumping matrix not implemented");
+#warning dumping matrix not implemented
+ }
+ virtual void visit(spirv_to_llvm::Array_type_descriptor &type) override
+ {
+ auto llvm_element_type = type.get_element_type()->get_or_make_type().type;
+ std::size_t element_size =
+ ::LLVMABISizeOfType(this_->data_layout.get(), llvm_element_type);
+ std::size_t element_count = type.get_element_count();
+ str += "[";
+ auto separator = "";
+ for(std::size_t i = 0; i < element_count; i++)
+ {
+ str += separator;
+ separator = ", ";
+ str = this_->append_value_to_string(
+ std::move(str),
+ *type.get_element_type(),
+ static_cast<const char *>(value) + i * element_size);
+ }
+ str += "]";
+ }
+ virtual void visit(spirv_to_llvm::Pointer_type_descriptor &type) override
+ {
+ str += "pointer:0x";
+ str = json::ast::Number_value::append_unsigned_integer_to_string(
+ reinterpret_cast<std::uint64_t>(*static_cast<const void *const *>(value)),
+ std::move(str),
+ 0x10);
+ }
+ virtual void visit(spirv_to_llvm::Function_type_descriptor &type) override
+ {
+ str += "function:0x";
+ str = json::ast::Number_value::append_unsigned_integer_to_string(
+ reinterpret_cast<std::uint64_t>(*static_cast<const void *const *>(value)),
+ std::move(str),
+ 0x10);
+ }
+ virtual void visit(spirv_to_llvm::Struct_type_descriptor &type) override
+ {
+ auto &&members = type.get_members(true);
+ auto llvm_type = type.get_or_make_type().type;
+ str += "{";
+ auto separator = "";
+ for(auto &member : members)
+ {
+ str += separator;
+ separator = ", ";
+ str = this_->append_value_to_string(
+ std::move(str),
+ *member.type,
+ static_cast<const char *>(value)
+ + ::LLVMOffsetOfElement(
+ this_->data_layout.get(), llvm_type, member.llvm_member_index));
+ }
+ str += "}";
+ }
+ };
+ type.visit(Visitor(this, str, value));
+ return str;
}
};
+void Graphics_pipeline::dump_vertex_shader_output_struct(const void *output_struct) const
+{
+ std::cerr << "output: "
+ << implementation->append_value_to_string(
+ {}, *implementation->vertex_shader_output_struct, output_struct)
+ << std::endl;
+}
+
std::unique_ptr<Graphics_pipeline> Graphics_pipeline::make(
Pipeline_cache *pipeline_cache, const VkGraphicsPipelineCreateInfo &create_info)
{
}
auto implementation = std::make_shared<Implementation>();
auto llvm_target_machine = llvm_wrapper::Target_machine::create_native_target_machine();
- std::vector<spirv_to_llvm::Converted_module> compiled_shaders;
- compiled_shaders.reserve(create_info.stageCount);
+ implementation->compiled_shaders.reserve(create_info.stageCount);
for(std::size_t i = 0; i < create_info.stageCount; i++)
{
auto &stage_info = create_info.pStages[i];
llvm_target_machine.get(),
shader_module->words(),
shader_module->word_count(),
- implementation->make_module_id(),
+ implementation->compiled_shaders.size(),
execution_model,
stage_info.pName);
std::cerr << "Translation to LLVM succeeded." << std::endl;
::LLVMVerifyModule(compiled_shader.module.get(), ::LLVMPrintMessageAction, nullptr);
if(failed)
throw std::runtime_error("LLVM module verification failed");
- compiled_shaders.push_back(std::move(compiled_shader));
+ implementation->compiled_shaders.push_back(std::move(compiled_shader));
}
- auto data_layout = llvm_target_machine.create_target_data_layout();
+ implementation->data_layout = llvm_target_machine.create_target_data_layout();
implementation->jit_stack = llvm_wrapper::Orc_jit_stack::create(std::move(llvm_target_machine));
Vertex_shader_function vertex_shader_function = nullptr;
std::size_t vertex_shader_output_struct_size = 0;
- for(auto &compiled_shader : compiled_shaders)
+ for(auto &compiled_shader : implementation->compiled_shaders)
{
vertex_shader_output_struct_size = implementation->jit_stack.add_eagerly_compiled_ir(
std::move(compiled_shader.module),
{
vertex_shader_function =
reinterpret_cast<Vertex_shader_function>(shader_entry_point_address);
+ implementation->vertex_shader_output_struct = compiled_shader.outputs_struct;
vertex_shader_output_struct_size = ::LLVMABISizeOfType(
- data_layout.get(), compiled_shader.outputs_struct->get_or_make_type().type);
+ implementation->data_layout.get(),
+ implementation->vertex_shader_output_struct->get_or_make_type().type);
#warning finish implementing Graphics_pipeline::make
continue;
}
void *output_buffer);
public:
- Vertex_shader_function get_vertex_shader_function() const noexcept
+ void run_vertex_shader(std::uint32_t vertex_start_index,
+ std::uint32_t vertex_end_index,
+ std::uint32_t instance_id,
+ void *output_buffer) const noexcept
{
- return vertex_shader_function;
+ vertex_shader_function(vertex_start_index, vertex_end_index, instance_id, output_buffer);
}
std::size_t get_vertex_shader_output_struct_size() const noexcept
{
return vertex_shader_output_struct_size;
}
+ void dump_vertex_shader_output_struct(const void *output_struct) const;
static std::unique_ptr<Graphics_pipeline> make(Pipeline_cache *pipeline_cache,
const VkGraphicsPipelineCreateInfo &create_info);
static std::unique_ptr<Graphics_pipeline> move_from_handle(VkPipeline pipeline) noexcept