mirror of
https://github.com/slendidev/lunar.git
synced 2025-12-10 11:29:51 +02:00
BIN
assets/basicmesh.glb
Normal file
BIN
assets/basicmesh.glb
Normal file
Binary file not shown.
BIN
assets/structure.glb
Normal file
BIN
assets/structure.glb
Normal file
Binary file not shown.
12
meson.build
12
meson.build
@@ -60,6 +60,17 @@ add_project_arguments(
|
|||||||
'-Wno-implicit-int-float-conversion',
|
'-Wno-implicit-int-float-conversion',
|
||||||
'-Wno-implicit-float-conversion',
|
'-Wno-implicit-float-conversion',
|
||||||
'-Wno-c++98-compat',
|
'-Wno-c++98-compat',
|
||||||
|
# Thanks fastgltf
|
||||||
|
'-Wno-ctad-maybe-unsupported',
|
||||||
|
'-Wno-switch-default',
|
||||||
|
'-Wno-nrvo',
|
||||||
|
'-Wno-shadow-field-in-constructor',
|
||||||
|
'-Wno-deprecated-copy-with-dtor',
|
||||||
|
'-Wno-double-promotion',
|
||||||
|
'-Wno-weak-vtables',
|
||||||
|
'-Wno-switch-enum',
|
||||||
|
'-Wno-sign-conversion',
|
||||||
|
'-Wno-documentation',
|
||||||
],
|
],
|
||||||
language : 'cpp'
|
language : 'cpp'
|
||||||
)
|
)
|
||||||
@@ -99,6 +110,7 @@ exe = executable('vr-compositor',
|
|||||||
'src/DescriptorLayoutBuilder.cpp',
|
'src/DescriptorLayoutBuilder.cpp',
|
||||||
'src/DescriptorAllocator.cpp',
|
'src/DescriptorAllocator.cpp',
|
||||||
'src/GraphicsPipelineBuilder.cpp',
|
'src/GraphicsPipelineBuilder.cpp',
|
||||||
|
'src/Loader.cpp',
|
||||||
'src/VulkanRenderer.cpp',
|
'src/VulkanRenderer.cpp',
|
||||||
'src/Application.cpp',
|
'src/Application.cpp',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
namespace Lunar {
|
namespace Lunar {
|
||||||
|
|
||||||
class VulkanRenderer;
|
struct VulkanRenderer;
|
||||||
|
|
||||||
struct Application {
|
struct Application {
|
||||||
Application();
|
Application();
|
||||||
|
|||||||
201
src/Loader.cpp
Normal file
201
src/Loader.cpp
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
#include "Loader.h"
|
||||||
|
|
||||||
|
#include <fastgltf/core.hpp>
|
||||||
|
#include <fastgltf/tools.hpp>
|
||||||
|
#include <fastgltf/util.hpp>
|
||||||
|
|
||||||
|
#include "VulkanRenderer.h"
|
||||||
|
|
||||||
|
namespace fastgltf {
|
||||||
|
template<>
|
||||||
|
struct ElementTraits<smath::Vec4>
|
||||||
|
: ElementTraitsBase<smath::Vec4, AccessorType::Vec4, float> { };
|
||||||
|
template<>
|
||||||
|
struct ElementTraits<smath::Vec3>
|
||||||
|
: ElementTraitsBase<smath::Vec3, AccessorType::Vec3, float> { };
|
||||||
|
template<>
|
||||||
|
struct ElementTraits<smath::Vec2>
|
||||||
|
: ElementTraitsBase<smath::Vec2, AccessorType::Vec2, float> { };
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef __cpp_lib_format_path
|
||||||
|
|
||||||
|
# include <filesystem>
|
||||||
|
# include <format>
|
||||||
|
# include <string>
|
||||||
|
# include <type_traits>
|
||||||
|
|
||||||
|
template<class CharT>
|
||||||
|
struct std::formatter<std::filesystem::path, CharT>
|
||||||
|
: std::formatter<std::basic_string<CharT>, CharT> {
|
||||||
|
template<class FormatContext>
|
||||||
|
auto format(std::filesystem::path const &p, FormatContext &ctx) const
|
||||||
|
{
|
||||||
|
std::basic_string<CharT> s;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<CharT, char>) {
|
||||||
|
s = p.generic_string();
|
||||||
|
} else if constexpr (std::is_same_v<CharT, wchar_t>) {
|
||||||
|
s = p.generic_wstring();
|
||||||
|
}
|
||||||
|
# if defined(__cpp_lib_char8_t)
|
||||||
|
else if constexpr (std::is_same_v<CharT, char8_t>) {
|
||||||
|
s = p.generic_u8string();
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
else if constexpr (std::is_same_v<CharT, char16_t>) {
|
||||||
|
s = p.generic_u16string();
|
||||||
|
} else if constexpr (std::is_same_v<CharT, char32_t>) {
|
||||||
|
s = p.generic_u32string();
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::formatter<std::basic_string<CharT>, CharT>::format(s, ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace Lunar {
|
||||||
|
|
||||||
|
auto Mesh::load_gltf_meshes(
|
||||||
|
VulkanRenderer &renderer, std::filesystem::path const path)
|
||||||
|
-> std::optional<std::vector<std::shared_ptr<Mesh>>>
|
||||||
|
{
|
||||||
|
renderer.logger().debug("Loading GLTF from file: {}", path);
|
||||||
|
|
||||||
|
auto data = fastgltf::GltfDataBuffer::FromPath(path);
|
||||||
|
if (data.error() != fastgltf::Error::None) {
|
||||||
|
renderer.logger().err("Failed to open glTF file: {} (error {})", path,
|
||||||
|
fastgltf::to_underlying(data.error()));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto gltfOptions { fastgltf::Options::LoadExternalBuffers };
|
||||||
|
|
||||||
|
fastgltf::Parser parser;
|
||||||
|
|
||||||
|
auto load { parser.loadGltf(data.get(), path.parent_path(), gltfOptions) };
|
||||||
|
if (load.error() != fastgltf::Error::None) {
|
||||||
|
renderer.logger().err(
|
||||||
|
"Failed to load glTF: {}", fastgltf::to_underlying(load.error()));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
fastgltf::Asset gltf { std::move(load.get()) };
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<Mesh>> meshes;
|
||||||
|
|
||||||
|
std::vector<uint32_t> indices;
|
||||||
|
std::vector<Vertex> vertices;
|
||||||
|
for (auto &mesh : gltf.meshes) {
|
||||||
|
Mesh new_mesh;
|
||||||
|
|
||||||
|
new_mesh.name = mesh.name;
|
||||||
|
|
||||||
|
indices.clear();
|
||||||
|
vertices.clear();
|
||||||
|
|
||||||
|
for (auto &&p : mesh.primitives) {
|
||||||
|
Surface new_surface;
|
||||||
|
new_surface.start_index = static_cast<uint32_t>(indices.size());
|
||||||
|
new_surface.count = static_cast<uint32_t>(
|
||||||
|
gltf.accessors[p.indicesAccessor.value()].count);
|
||||||
|
|
||||||
|
size_t initial_vertex = vertices.size();
|
||||||
|
|
||||||
|
{ // Indices
|
||||||
|
auto &accessor = gltf.accessors[p.indicesAccessor.value()];
|
||||||
|
indices.reserve(indices.size() + accessor.count);
|
||||||
|
fastgltf::iterateAccessor<std::uint32_t>(
|
||||||
|
gltf, accessor, [&](std::uint32_t idx) {
|
||||||
|
indices.emplace_back(idx + initial_vertex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Vertex positions
|
||||||
|
auto &accessor = gltf.accessors[p.findAttribute("POSITION")
|
||||||
|
->accessorIndex];
|
||||||
|
|
||||||
|
for (auto pos :
|
||||||
|
fastgltf::iterateAccessor<smath::Vec3>(gltf, accessor)) {
|
||||||
|
Vertex v {
|
||||||
|
.position = pos,
|
||||||
|
.u = 0,
|
||||||
|
.normal = { 0, 0, 0 },
|
||||||
|
.v = 0,
|
||||||
|
.color = { 1.0f, 1.0f, 1.0f, 1.0f },
|
||||||
|
};
|
||||||
|
vertices.emplace_back(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto attr = p.findAttribute("NORMAL")) { // Normals
|
||||||
|
auto &accessor = gltf.accessors[attr->accessorIndex];
|
||||||
|
size_t local_index = 0;
|
||||||
|
for (auto normal :
|
||||||
|
fastgltf::iterateAccessor<smath::Vec3>(gltf, accessor)) {
|
||||||
|
vertices[initial_vertex + local_index].normal = normal;
|
||||||
|
local_index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto attr = p.findAttribute("TEXCOORD_0")) { // UVs
|
||||||
|
auto &accessor = gltf.accessors[attr->accessorIndex];
|
||||||
|
size_t local_index = 0;
|
||||||
|
for (auto uv :
|
||||||
|
fastgltf::iterateAccessor<smath::Vec2>(gltf, accessor)) {
|
||||||
|
uv.unpack(vertices[initial_vertex + local_index].u,
|
||||||
|
vertices[initial_vertex + local_index].v);
|
||||||
|
local_index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto attr = p.findAttribute("COLOR_0")) { // Colors
|
||||||
|
auto &accessor = gltf.accessors[attr->accessorIndex];
|
||||||
|
size_t local_index = 0;
|
||||||
|
|
||||||
|
switch (accessor.type) {
|
||||||
|
case fastgltf::AccessorType::Vec3: {
|
||||||
|
for (auto c3 : fastgltf::iterateAccessor<smath::Vec3>(
|
||||||
|
gltf, accessor)) {
|
||||||
|
auto &dst
|
||||||
|
= vertices[initial_vertex + local_index].color;
|
||||||
|
dst = { c3.x(), c3.y(), c3.z(), 1.0f };
|
||||||
|
++local_index;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case fastgltf::AccessorType::Vec4: {
|
||||||
|
for (auto c4 : fastgltf::iterateAccessor<smath::Vec4>(
|
||||||
|
gltf, accessor)) {
|
||||||
|
vertices[initial_vertex + local_index].color = c4;
|
||||||
|
++local_index;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
renderer.logger().warn(
|
||||||
|
"Unsupported COLOR_0 accessor type ({}) on mesh '{}'",
|
||||||
|
static_cast<int>(accessor.type), new_mesh.name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool OVERRIDE_COLORS = true;
|
||||||
|
if (OVERRIDE_COLORS) {
|
||||||
|
for (auto &vtx : vertices) {
|
||||||
|
vtx.color = smath::Vec4(vtx.normal, 1.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new_mesh.surfaces.emplace_back(new_surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
new_mesh.mesh_buffers = renderer.upload_mesh(indices, vertices);
|
||||||
|
|
||||||
|
meshes.emplace_back(std::make_shared<Mesh>(std::move(new_mesh)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return meshes;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __cpp_lib_format_path
|
||||||
|
|
||||||
|
} // namespace Lunar
|
||||||
30
src/Loader.h
Normal file
30
src/Loader.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Types.h"
|
||||||
|
|
||||||
|
namespace Lunar {
|
||||||
|
|
||||||
|
struct VulkanRenderer;
|
||||||
|
|
||||||
|
struct Mesh {
|
||||||
|
struct Surface {
|
||||||
|
uint32_t start_index;
|
||||||
|
uint32_t count;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
std::vector<Surface> surfaces;
|
||||||
|
GPUMeshBuffers mesh_buffers;
|
||||||
|
|
||||||
|
static auto load_gltf_meshes(
|
||||||
|
VulkanRenderer &renderer, std::filesystem::path const path)
|
||||||
|
-> std::optional<std::vector<std::shared_ptr<Mesh>>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Lunar
|
||||||
47
src/Types.h
Normal file
47
src/Types.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <smath.hpp>
|
||||||
|
#include <vk_mem_alloc.h>
|
||||||
|
#include <vulkan/vulkan_core.h>
|
||||||
|
|
||||||
|
#include "DeletionQueue.h"
|
||||||
|
|
||||||
|
namespace Lunar {
|
||||||
|
|
||||||
|
struct AllocatedImage {
|
||||||
|
VkImage image;
|
||||||
|
VkImageView image_view;
|
||||||
|
VmaAllocation allocation;
|
||||||
|
VkExtent3D extent;
|
||||||
|
VkFormat format;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AllocatedBuffer {
|
||||||
|
VkBuffer buffer;
|
||||||
|
VmaAllocation allocation;
|
||||||
|
VmaAllocationInfo info;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FrameData {
|
||||||
|
VkCommandPool command_pool;
|
||||||
|
VkCommandBuffer main_command_buffer;
|
||||||
|
VkSemaphore swapchain_semaphore;
|
||||||
|
VkFence render_fence;
|
||||||
|
|
||||||
|
DeletionQueue deletion_queue;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Vertex {
|
||||||
|
smath::Vec3 position;
|
||||||
|
float u;
|
||||||
|
smath::Vec3 normal;
|
||||||
|
float v;
|
||||||
|
smath::Vec4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GPUMeshBuffers {
|
||||||
|
AllocatedBuffer index_buffer, vertex_buffer;
|
||||||
|
VkDeviceAddress vertex_buffer_address;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Lunar
|
||||||
@@ -151,7 +151,8 @@ auto VulkanRenderer::vk_init() -> void
|
|||||||
features_13.pNext = nullptr;
|
features_13.pNext = nullptr;
|
||||||
features_13.synchronization2 = VK_TRUE;
|
features_13.synchronization2 = VK_TRUE;
|
||||||
features_13.dynamicRendering = VK_TRUE;
|
features_13.dynamicRendering = VK_TRUE;
|
||||||
VkPhysicalDeviceBufferDeviceAddressFeatures buffer_device_address_features {};
|
VkPhysicalDeviceBufferDeviceAddressFeatures
|
||||||
|
buffer_device_address_features {};
|
||||||
buffer_device_address_features.sType
|
buffer_device_address_features.sType
|
||||||
= VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES;
|
= VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES;
|
||||||
buffer_device_address_features.bufferDeviceAddress = VK_TRUE;
|
buffer_device_address_features.bufferDeviceAddress = VK_TRUE;
|
||||||
@@ -587,7 +588,15 @@ auto VulkanRenderer::default_data_init() -> void
|
|||||||
|
|
||||||
m_vk.rectangle = upload_mesh(rect_indices, rect_vertices);
|
m_vk.rectangle = upload_mesh(rect_indices, rect_vertices);
|
||||||
|
|
||||||
|
m_vk.test_meshes
|
||||||
|
= Mesh::load_gltf_meshes(*this, "assets/basicmesh.glb").value();
|
||||||
|
|
||||||
m_vk.deletion_queue.emplace([&]() {
|
m_vk.deletion_queue.emplace([&]() {
|
||||||
|
for (auto &mesh : m_vk.test_meshes) {
|
||||||
|
destroy_buffer(mesh->mesh_buffers.index_buffer);
|
||||||
|
destroy_buffer(mesh->mesh_buffers.vertex_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
destroy_buffer(m_vk.rectangle.index_buffer);
|
destroy_buffer(m_vk.rectangle.index_buffer);
|
||||||
destroy_buffer(m_vk.rectangle.vertex_buffer);
|
destroy_buffer(m_vk.rectangle.vertex_buffer);
|
||||||
});
|
});
|
||||||
@@ -763,6 +772,25 @@ auto VulkanRenderer::draw_geometry(VkCommandBuffer cmd) -> void
|
|||||||
|
|
||||||
vkCmdDrawIndexed(cmd, 6, 1, 0, 0, 0);
|
vkCmdDrawIndexed(cmd, 6, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
push_constants.vertex_buffer
|
||||||
|
= m_vk.test_meshes[2]->mesh_buffers.vertex_buffer_address;
|
||||||
|
|
||||||
|
auto const view { smath::translate(smath::Vec3 { 0, 0, -0.1 }) };
|
||||||
|
auto projection { smath::matrix_perspective(smath::deg(70.0f),
|
||||||
|
static_cast<float>(m_vk.draw_extent.width)
|
||||||
|
/ static_cast<float>(m_vk.draw_extent.height),
|
||||||
|
10000.0f, 0.1f) };
|
||||||
|
push_constants.world_matrix = projection * view;
|
||||||
|
|
||||||
|
vkCmdPushConstants(cmd, m_vk.mesh_pipeline_layout,
|
||||||
|
VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(push_constants), &push_constants);
|
||||||
|
vkCmdBindIndexBuffer(cmd,
|
||||||
|
m_vk.test_meshes[2]->mesh_buffers.index_buffer.buffer, 0,
|
||||||
|
VK_INDEX_TYPE_UINT32);
|
||||||
|
|
||||||
|
vkCmdDrawIndexed(cmd, m_vk.test_meshes[2]->surfaces[0].count, 1,
|
||||||
|
m_vk.test_meshes[2]->surfaces[0].start_index, 0, 0);
|
||||||
|
|
||||||
vkCmdEndRendering(cmd);
|
vkCmdEndRendering(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,46 +11,12 @@
|
|||||||
|
|
||||||
#include "DeletionQueue.h"
|
#include "DeletionQueue.h"
|
||||||
#include "DescriptorAllocator.h"
|
#include "DescriptorAllocator.h"
|
||||||
|
#include "Loader.h"
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
|
#include "Types.h"
|
||||||
|
|
||||||
namespace Lunar {
|
namespace Lunar {
|
||||||
|
|
||||||
struct AllocatedImage {
|
|
||||||
VkImage image;
|
|
||||||
VkImageView image_view;
|
|
||||||
VmaAllocation allocation;
|
|
||||||
VkExtent3D extent;
|
|
||||||
VkFormat format;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AllocatedBuffer {
|
|
||||||
VkBuffer buffer;
|
|
||||||
VmaAllocation allocation;
|
|
||||||
VmaAllocationInfo info;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FrameData {
|
|
||||||
VkCommandPool command_pool;
|
|
||||||
VkCommandBuffer main_command_buffer;
|
|
||||||
VkSemaphore swapchain_semaphore;
|
|
||||||
VkFence render_fence;
|
|
||||||
|
|
||||||
DeletionQueue deletion_queue;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Vertex {
|
|
||||||
smath::Vec3 position;
|
|
||||||
float uv_x;
|
|
||||||
smath::Vec3 normal;
|
|
||||||
float uv_y;
|
|
||||||
smath::Vec4 color;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GPUMeshBuffers {
|
|
||||||
AllocatedBuffer index_buffer, vertex_buffer;
|
|
||||||
VkDeviceAddress vertex_buffer_address;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GPUDrawPushConstants {
|
struct GPUDrawPushConstants {
|
||||||
smath::Mat4 world_matrix;
|
smath::Mat4 world_matrix;
|
||||||
VkDeviceAddress vertex_buffer;
|
VkDeviceAddress vertex_buffer;
|
||||||
@@ -58,8 +24,7 @@ struct GPUDrawPushConstants {
|
|||||||
|
|
||||||
constexpr unsigned FRAME_OVERLAP = 2;
|
constexpr unsigned FRAME_OVERLAP = 2;
|
||||||
|
|
||||||
class VulkanRenderer {
|
struct VulkanRenderer {
|
||||||
public:
|
|
||||||
VulkanRenderer(SDL_Window *window, Logger &logger);
|
VulkanRenderer(SDL_Window *window, Logger &logger);
|
||||||
~VulkanRenderer();
|
~VulkanRenderer();
|
||||||
|
|
||||||
@@ -68,6 +33,10 @@ public:
|
|||||||
|
|
||||||
auto immediate_submit(std::function<void(VkCommandBuffer cmd)> &&function)
|
auto immediate_submit(std::function<void(VkCommandBuffer cmd)> &&function)
|
||||||
-> void;
|
-> void;
|
||||||
|
auto upload_mesh(std::span<uint32_t> indices, std::span<Vertex> vertices)
|
||||||
|
-> GPUMeshBuffers;
|
||||||
|
|
||||||
|
auto logger() const -> Logger & { return m_logger; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
auto vk_init() -> void;
|
auto vk_init() -> void;
|
||||||
@@ -96,8 +65,6 @@ private:
|
|||||||
auto create_buffer(size_t alloc_size, VkBufferUsageFlags usage,
|
auto create_buffer(size_t alloc_size, VkBufferUsageFlags usage,
|
||||||
VmaMemoryUsage memory_usage) -> AllocatedBuffer;
|
VmaMemoryUsage memory_usage) -> AllocatedBuffer;
|
||||||
auto destroy_buffer(AllocatedBuffer &buffer) -> void;
|
auto destroy_buffer(AllocatedBuffer &buffer) -> void;
|
||||||
auto upload_mesh(std::span<uint32_t> indices, std::span<Vertex> vertices)
|
|
||||||
-> GPUMeshBuffers;
|
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
vkb::Instance instance;
|
vkb::Instance instance;
|
||||||
@@ -154,6 +121,8 @@ private:
|
|||||||
VkCommandPool imm_command_pool {};
|
VkCommandPool imm_command_pool {};
|
||||||
|
|
||||||
uint64_t frame_number { 0 };
|
uint64_t frame_number { 0 };
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<Mesh>> test_meshes;
|
||||||
} m_vk;
|
} m_vk;
|
||||||
|
|
||||||
SDL_Window *m_window { nullptr };
|
SDL_Window *m_window { nullptr };
|
||||||
|
|||||||
2
thirdparty/smath
vendored
2
thirdparty/smath
vendored
Submodule thirdparty/smath updated: e32204db0c...b5e0aabe37
Reference in New Issue
Block a user