Compare commits

...

4 Commits

  1. 16
      imgui.ini
  2. 12
      include/application.hpp
  3. 1
      include/constraints.hpp
  4. 2
      include/soft_body.hpp
  5. 30
      include/vulkan/buffer.hpp
  6. 244
      src/application.cpp
  7. 33
      src/constraints.cpp
  8. 2
      src/soft_body.cpp
  9. 126
      src/vulkan/buffer.cpp
  10. 4
      src/vulkan/image.cpp
  11. 14
      src/vulkan/instance.cpp

@ -30,16 +30,24 @@ Collapsed=0
[Window][Performance] [Window][Performance]
Pos=1716,2 Pos=1716,2
Size=203,1002 Size=203,345
Collapsed=0 Collapsed=0
DockId=0x00000001,0 DockId=0x00000002,0
[Window][Performance 2] [Window][Performance 2]
Pos=1617,2 Pos=1617,2
Size=302,1002 Size=302,1002
Collapsed=0 Collapsed=0
DockId=0x00000001,1 DockId=0x00000002,1
[Window][Scene]
Pos=1716,349
Size=203,655
Collapsed=0
DockId=0x00000004,0
[Docking][Data] [Docking][Data]
DockNode ID=0x00000001 Pos=1716,2 Size=203,1002 Selected=0x60B79D0E DockNode ID=0x00000001 Pos=1716,2 Size=203,1002 Split=Y Selected=0x60B79D0E
DockNode ID=0x00000002 Parent=0x00000001 SizeRef=203,345 Selected=0x60B79D0E
DockNode ID=0x00000004 Parent=0x00000001 SizeRef=203,655 Selected=0xE192E354

@ -23,12 +23,14 @@
using std::unique_ptr, std::make_unique; using std::unique_ptr, std::make_unique;
using std::vector; using std::vector;
using std::optional;
class SoftBody; class SoftBody;
class Instance; class Instance;
class Swapchain; class Swapchain;
class GraphicsPipeline; class GraphicsPipeline;
class Buffer; class Buffer;
class Buffer;
class CommandPool; class CommandPool;
class Image; class Image;
class ComputePipeline; class ComputePipeline;
@ -37,6 +39,7 @@ class Semaphore;
class Camera; class Camera;
class DescriptorPool; class DescriptorPool;
class Grabber; class Grabber;
struct SizesUniformData;
class Application { class Application {
public: public:
@ -61,8 +64,9 @@ private:
unique_ptr<Semaphore> transferSemaphore; unique_ptr<Semaphore> transferSemaphore;
unique_ptr<Fence> renderFence; unique_ptr<Fence> renderFence;
unique_ptr<Fence> computeFence; unique_ptr<Fence> computeFence;
unique_ptr<Fence> transferFence; unique_ptr<Fence> computeTransferredFence;
std::mutex submitMutex; std::mutex submitMutex;
std::mutex bufferWriteMutex;
unique_ptr<Swapchain> swapchain; unique_ptr<Swapchain> swapchain;
unique_ptr<DescriptorPool> descriptorPool; unique_ptr<DescriptorPool> descriptorPool;
@ -75,7 +79,10 @@ private:
unique_ptr<Grabber> grabber; unique_ptr<Grabber> grabber;
unique_ptr<Buffer> grabBuffer; unique_ptr<Buffer> grabBuffer;
void createMeshBuffers(); void addSoftBody(const std::string& modelFile, size_t count=1);
void removeSoftBody(const unique_ptr<SoftBody>& softBody);
void updateConstraintBuffers(VkCommandBuffer commandBuffer);
size_t currentDrawVertexBuffer = 0; size_t currentDrawVertexBuffer = 0;
unique_ptr<Buffer> vertexBuffers[2]; unique_ptr<Buffer> vertexBuffers[2];
unique_ptr<Buffer> faceBuffer; unique_ptr<Buffer> faceBuffer;
@ -86,6 +93,7 @@ private:
vector<unique_ptr<SoftBody>> softBodies; vector<unique_ptr<SoftBody>> softBodies;
unique_ptr<Buffer> sizeInformationBuffer; unique_ptr<Buffer> sizeInformationBuffer;
SizesUniformData *sizeInformation = nullptr;
unique_ptr<Buffer> simulationPropertiesBuffer; unique_ptr<Buffer> simulationPropertiesBuffer;

@ -51,6 +51,7 @@ struct ConstraintData {
void recordNewPartition(); void recordNewPartition();
void writePartitionInformation(); void writePartitionInformation();
void insert(const ConstraintData& other);
private: private:
uint32_t prePartitionEdgeCount; uint32_t prePartitionEdgeCount;
uint32_t prePartitionTetrahedronCount; uint32_t prePartitionTetrahedronCount;

@ -28,7 +28,7 @@ public:
vector<Face> faces; vector<Face> faces;
ConstraintData constraintData; ConstraintData constraintData;
void applyVertexOffset(const glm::vec3& offset); void applyVertexWorldOffset(const glm::vec3& offset);
SoftBody& operator =(const SoftBody& other) = delete; SoftBody& operator =(const SoftBody& other) = delete;
private: private:

@ -4,27 +4,43 @@
#include <stdexcept> #include <stdexcept>
#include "vertex.hpp" #include "vertex.hpp"
#include "vk_mem_alloc.h" #include "vk_mem_alloc.h"
#include <memory>
#include <optional>
using std::unique_ptr;
using std::shared_ptr;
using std::make_shared;
using std::make_unique;
using std::optional;
class Image; class Image;
class Buffer { class Buffer {
public: public:
explicit Buffer(VkDeviceSize bufferSize, VkBufferUsageFlags bufferUsage, Buffer(VkDeviceSize bufferSize, VkBufferUsageFlags bufferUsage,
VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags vmaAllocationFlags);
explicit Buffer(VkDeviceSize bufferSize, void* initialData, VkDeviceSize initialDataSize, VkBufferUsageFlags bufferUsage,
VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags vmaAllocationFlags); VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags vmaAllocationFlags);
~Buffer(); unique_ptr<Buffer> appended(void* data, VkDeviceSize appendSize, VkCommandBuffer commandBuffer) const;
unique_ptr<Buffer> replaced(void* data, VkDeviceSize replaceSize, VkCommandBuffer commandBuffer) const;
virtual ~Buffer();
VkBuffer handle = VK_NULL_HANDLE; VkBuffer handle = VK_NULL_HANDLE;
VmaAllocation allocation = VK_NULL_HANDLE; VmaAllocation allocation = VK_NULL_HANDLE;
VmaAllocationInfo allocationInfo {}; VmaAllocationInfo allocationInfo {};
VkDeviceSize size; VkDeviceSize size;
void setName(const std::string& name); void setName(const std::string& newName);
template <typename T> template <typename T>
T& access(){ T& access(){
return *reinterpret_cast<T*>(allocationInfo.pMappedData); return *reinterpret_cast<T*>(allocationInfo.pMappedData);
} }
void copyTo(Buffer* buffer) const;
void copyTo(Image* image) const;
void setData(void* data, VkDeviceSize offset, VkDeviceSize dataSize, VkCommandBuffer commandBuffer=VK_NULL_HANDLE);
shared_ptr<Buffer> getStagingBuffer();
Buffer(const Buffer& other) = delete; Buffer(const Buffer& other) = delete;
Buffer& operator =(const Buffer& other) = delete; Buffer& operator =(const Buffer& other) = delete;
void copyTo(Buffer* buffer); private:
void copyTo(Image* image); optional<shared_ptr<Buffer>> stagingBufferOptional;
std::string name;
VkBufferUsageFlags bufferUsageFlags;
VmaMemoryUsage memoryUsage;
VmaAllocationCreateFlags allocationCreateFlags;
}; };

@ -81,22 +81,23 @@ Application::Application() {
bool grabbing; bool grabbing;
} initialGrabInformation {}; } initialGrabInformation {};
initialGrabInformation.distanceToFace = 1e20; initialGrabInformation.distanceToFace = 1e20;
grabBuffer = make_unique<Buffer>(sizeof(GrabInformation), &initialGrabInformation, sizeof(GrabInformation), grabBuffer = make_unique<Buffer>(sizeof(GrabInformation),
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0); VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0);
grabBuffer->setName("Grab"); grabBuffer->setName("Grab");
grabBuffer->setData(&initialGrabInformation, 0, sizeof(initialGrabInformation));
grabber = make_unique<Grabber>(); grabber = make_unique<Grabber>();
createMeshBuffers();
SizesUniformData sizeInformation {};
sizeInformation.vertexCount = vertexBuffers[0]->size / sizeof(Vertex);
sizeInformation.faceCount = faceBuffer->size / sizeof(Face);
sizeInformationBuffer = make_unique<Buffer>( sizeInformationBuffer = make_unique<Buffer>(
sizeof(SizesUniformData), &sizeInformation, sizeof(sizeInformation), sizeof(SizesUniformData),
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0); VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT);
sizeInformationBuffer->setName("Sizes"); sizeInformationBuffer->setName("Sizes");
sizeInformation = &sizeInformationBuffer->access<SizesUniformData>();
*sizeInformation = {};
addSoftBody("models/bunny_medium.ply", 10);
SimulationUniformData simulationUniformData { SimulationUniformData simulationUniformData {
.gravity = {0, -9.81, 0}, .gravity = {0, -9.81, 0},
@ -124,13 +125,6 @@ Application::Application() {
descriptorPool->bindBuffer(*cameraUniformBuffer, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, DescriptorSet::WORLD, 0); descriptorPool->bindBuffer(*cameraUniformBuffer, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, DescriptorSet::WORLD, 0);
descriptorPool->bindImage(*firstImage, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, DescriptorSet::WORLD, 2); descriptorPool->bindImage(*firstImage, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, DescriptorSet::WORLD, 2);
descriptorPool->bindBuffer(*vertexBuffers[1 - currentDrawVertexBuffer], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DescriptorSet::MESH, 0);
descriptorPool->bindBuffer(*faceBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DescriptorSet::MESH, 1);
descriptorPool->bindBuffer(*edgeBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DescriptorSet::MESH, 2);
descriptorPool->bindBuffer(*triangleBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DescriptorSet::MESH, 3);
descriptorPool->bindBuffer(*tetrahedronBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DescriptorSet::MESH, 4);
descriptorPool->bindBuffer(*sizeInformationBuffer, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, DescriptorSet::MESH, 5);
descriptorPool->bindBuffer(*simulationPropertiesBuffer, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, DescriptorSet::SIMULATION, 0); descriptorPool->bindBuffer(*simulationPropertiesBuffer, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, DescriptorSet::SIMULATION, 0);
descriptorPool->bindBuffer(*grabBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DescriptorSet::SIMULATION, 1); descriptorPool->bindBuffer(*grabBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DescriptorSet::SIMULATION, 1);
@ -148,7 +142,8 @@ void Application::mainLoop() {
auto t2 = system_clock::now(); auto t2 = system_clock::now();
auto measuredUpdateDuration = duration<float>(t2 - t1); auto measuredUpdateDuration = duration<float>(t2 - t1);
auto requestedUpdateDuration = duration<float>(simulationPropertiesBuffer->access<SimulationUniformData>().dt); auto requestedUpdateDuration = duration<float>(
simulationPropertiesBuffer->access<SimulationUniformData>().dt);
std::this_thread::sleep_for(requestedUpdateDuration - measuredUpdateDuration); std::this_thread::sleep_for(requestedUpdateDuration - measuredUpdateDuration);
performanceInformation.updateDuration = measuredUpdateDuration.count(); performanceInformation.updateDuration = measuredUpdateDuration.count();
@ -180,131 +175,151 @@ void Application::createSyncObjects() {
transferSemaphore = make_unique<Semaphore>(); transferSemaphore = make_unique<Semaphore>();
renderFence = make_unique<Fence>(true); renderFence = make_unique<Fence>(true);
computeFence = make_unique<Fence>(false); computeFence = make_unique<Fence>(false);
transferFence = make_unique<Fence>(false); computeTransferredFence = make_unique<Fence>(false);
} }
void Application::createMeshBuffers() { void Application::addSoftBody(const std::string &modelFile, size_t count) {
Mesh sphere("models/icosphere_medium.ply"); bufferWriteMutex.lock();
Mesh bunny("models/bunny_medium.ply");
auto body = std::make_unique<SoftBody>(&sphere, 1.f / 60); Mesh mesh(modelFile);
for (size_t i = 0; i < 10; i++){ // Do SoftBody calculations once in constructor, will be copied from now on
auto copy = std::make_unique<SoftBody>(*body.get()); auto original = std::make_unique<SoftBody>(&mesh, 1.f / 60);
copy->applyVertexOffset({i * 2, 0, 0});
softBodies.push_back(std::move(copy));
}
body = std::make_unique<SoftBody>(&bunny, 1.f / 10); vector<Vertex> newVertices;
for (size_t i = 0; i < 10; i++){ vector<Face> newFaces;
auto copy = std::make_unique<SoftBody>(*body.get());
copy->applyVertexOffset({i * 2, 0, 2}); for (size_t i = 0; i < count; i++){
softBodies.push_back(std::move(copy));
}
vector<Vertex> vertices; // Local copy
vector<Face> faces; auto softBody = std::make_unique<SoftBody>(*original.get());
for (std::unique_ptr<SoftBody> &currentSoftBody : softBodies){ softBody->applyVertexWorldOffset({i * 2, 0, 0});
currentSoftBody->firstIndex = faces.size() * 3;
currentSoftBody->vertexOffset = static_cast<int32_t>(vertices.size());
int32_t vertexOffset = currentSoftBody->vertexOffset; // Position in face buffer
softBody->firstIndex = (sizeInformation->faceCount + newFaces.size()) * 3;
for (auto &face : currentSoftBody->faces){ // Position in vertex buffer
softBody->vertexOffset = static_cast<int32_t>(sizeInformation->vertexCount + newVertices.size());
// Vertex offset added manually for easy access in Compute Shaders
{
auto vertexOffset = softBody->vertexOffset;
for (auto &face : softBody->faces){
face.a += vertexOffset; face.a += vertexOffset;
face.b += vertexOffset; face.b += vertexOffset;
face.c += vertexOffset; face.c += vertexOffset;
} }
for (auto &edge : softBody->constraintData.edges){
for (auto &edge : currentSoftBody->constraintData.edges){
edge.a += vertexOffset; edge.a += vertexOffset;
edge.b += vertexOffset; edge.b += vertexOffset;
} }
for (auto &triangle : softBody->constraintData.triangles){
for (auto &triangle : currentSoftBody->constraintData.triangles){
triangle.a += vertexOffset; triangle.a += vertexOffset;
triangle.b += vertexOffset; triangle.b += vertexOffset;
triangle.c += vertexOffset; triangle.c += vertexOffset;
} }
for (auto &tetrahedron : softBody->constraintData.tetrahedra){
for (auto &tetrahedron : currentSoftBody->constraintData.tetrahedra){
tetrahedron.a += vertexOffset; tetrahedron.a += vertexOffset;
tetrahedron.b += vertexOffset; tetrahedron.b += vertexOffset;
tetrahedron.c += vertexOffset; tetrahedron.c += vertexOffset;
tetrahedron.d += vertexOffset; tetrahedron.d += vertexOffset;
} }
}
vertices.insert(vertices.end(), currentSoftBody->vertices.begin(), currentSoftBody->vertices.end()); // Append data to vertices and faces
faces.insert(faces.end(), currentSoftBody->faces.begin(), currentSoftBody->faces.end()); newVertices.insert(newVertices.end(), softBody->vertices.begin(), softBody->vertices.end());
newFaces.insert(newFaces.end(), softBody->faces.begin(), softBody->faces.end());
constraintData.partitionCount = std::max(constraintData.partitionCount, currentSoftBody->constraintData.partitionCount); // Insert data at the right places in this->constraintData
constraintData.insert(softBody->constraintData);
auto combine = [&currentSoftBody, this] ( softBodies.push_back(std::move(softBody));
auto &globalIndices, auto &bodyIndices, }
vector<ConstraintData::Partition> &globalPartitions, vector<ConstraintData::Partition> &bodyPartitions){
if (globalPartitions.size() < currentSoftBody->constraintData.partitionCount)
globalPartitions.resize(currentSoftBody->constraintData.partitionCount);
uint32_t offsetAdded = 0; sizeInformation->vertexCount += newVertices.size();
sizeInformation->faceCount += newFaces.size();
for (uint32_t partition = 0; partition < constraintData.partitionCount; partition++){ unique_ptr<Buffer> newVertexBuffers[2];
unique_ptr<Buffer> newFaceBuffer;
unique_ptr<Buffer> newEdgeBuffer;
unique_ptr<Buffer> newTetrahedronBuffer;
ConstraintData::Partition &globalPartition = globalPartitions[partition]; auto commandBuffer = Instance::instance->renderingCommandPool->beginSingleTimeCommandBuffer();
globalPartition.offset += offsetAdded;
if (partition < bodyPartitions.size()){ VkBufferUsageFlags bufferUsageFlags = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
const ConstraintData::Partition &bodyPartition = bodyPartitions[partition]; VmaMemoryUsage memoryUsage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
auto dst = globalIndices.begin() + globalPartition.offset; if (vertexBuffers[0] == nullptr){
auto srcStart = bodyIndices.begin() + bodyPartition.offset; newVertexBuffers[0] = make_unique<Buffer>(newVertices.size() * sizeof(Vertex),
uint32_t count = bodyPartition.size; bufferUsageFlags | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
globalIndices.insert(dst, srcStart, srcStart + count); memoryUsage, 0);
newVertexBuffers[1] = make_unique<Buffer>(newVertices.size() * sizeof(Vertex),
bufferUsageFlags | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
memoryUsage, 0);
newFaceBuffer = make_unique<Buffer>(newFaces.size() * sizeof(Face),
bufferUsageFlags | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
memoryUsage, 0);
newEdgeBuffer = make_unique<Buffer>(constraintData.edges.size() * sizeof(Edge), bufferUsageFlags, memoryUsage, 0);
newTetrahedronBuffer = make_unique<Buffer>(constraintData.tetrahedra.size() * sizeof(Tetrahedron), bufferUsageFlags, memoryUsage, 0);
globalPartition.size += count; newVertexBuffers[0]->setName("Vertices 0");
newVertexBuffers[1]->setName("Vertices 1");
newFaceBuffer->setName("Faces");
newEdgeBuffer->setName("Edges");
newTetrahedronBuffer->setName("Tetrahedra");
offsetAdded += count; newVertexBuffers[0]->setData(newVertices.data(), 0, newVertexBuffers[0]->size, commandBuffer);
newVertexBuffers[1]->setData(newVertices.data(), 0, newVertexBuffers[1]->size, commandBuffer);
newFaceBuffer->setData(newFaces.data(), 0, newFaceBuffer->size, commandBuffer);
newEdgeBuffer->setData(constraintData.edges.data(), 0, newEdgeBuffer->size, commandBuffer);
newTetrahedronBuffer->setData(constraintData.tetrahedra.data(), 0, newTetrahedronBuffer->size, commandBuffer);
} else {
newVertexBuffers[0] = vertexBuffers[0]->appended(newVertices.data(), newVertices.size() * sizeof(Vertex), commandBuffer);
newVertexBuffers[1] = vertexBuffers[1]->appended(newVertices.data(), newVertices.size() * sizeof(Vertex), commandBuffer);
newFaceBuffer = faceBuffer->appended(newFaces.data(), newFaces.size() * sizeof(Face), commandBuffer);
newEdgeBuffer = edgeBuffer->replaced(constraintData.edges.data(), constraintData.edges.size() * sizeof(Edge), commandBuffer);
newTetrahedronBuffer = tetrahedronBuffer->replaced(constraintData.tetrahedra.data(), constraintData.tetrahedra.size() * sizeof(Tetrahedron), commandBuffer);
} }
}
};
combine(constraintData.edges, currentSoftBody->constraintData.edges, VkQueue queue = Instance::instance->graphicsAndPresentQueue;
constraintData.edgePartitions, currentSoftBody->constraintData.edgePartitions); Instance::instance->renderingCommandPool->endSingleTimeCommandBuffer(commandBuffer, queue);
combine(constraintData.tetrahedra, currentSoftBody->constraintData.tetrahedra, vertexBuffers[0] = std::move(newVertexBuffers[0]);
constraintData.tetrahedronPartitions, currentSoftBody->constraintData.tetrahedronPartitions); vertexBuffers[1] = std::move(newVertexBuffers[1]);
faceBuffer = std::move(newFaceBuffer);
edgeBuffer = std::move(newEdgeBuffer);
tetrahedronBuffer = std::move(newTetrahedronBuffer);
descriptorPool->bindBuffer(*vertexBuffers[1 - currentDrawVertexBuffer], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DescriptorSet::MESH, 0);
descriptorPool->bindBuffer(*faceBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DescriptorSet::MESH, 1);
descriptorPool->bindBuffer(*edgeBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DescriptorSet::MESH, 2);
descriptorPool->bindBuffer(*tetrahedronBuffer, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DescriptorSet::MESH, 4);
descriptorPool->bindBuffer(*sizeInformationBuffer, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, DescriptorSet::MESH, 5);
constraintData.triangles.insert(constraintData.triangles.end(), currentSoftBody->constraintData.triangles.begin(), currentSoftBody->constraintData.triangles.end()); bufferWriteMutex.unlock();
constraintData.tetrahedra.insert(constraintData.tetrahedra.end(), currentSoftBody->constraintData.tetrahedra.begin(), currentSoftBody->constraintData.tetrahedra.end());
} }
printf("Vertices: %zu\nFaces: %zu\nEdges: %zu\nTriangles: %zu\nTetrahedra: %zu\nTotal Constraints: %zu\n", void Application::removeSoftBody(const unique_ptr<SoftBody> &softBody) {
vertices.size(), faces.size(), constraintData.edges.size(), constraintData.triangles.size(), constraintData.tetrahedra.size(), // cpu: remove in constraintData, reduce partition sizes and offsets
constraintData.edges.size() + constraintData.tetrahedra.size()); // cpu: reduce firstIndex and vertexOffset in following bodies
printf("Partitions: %u\n", constraintData.partitionCount);
class SimulationBuffer : public Buffer { // gpu: update constraintData
public: // gpu: update vertices and faces, take from remaining softbodies
SimulationBuffer(void* data, VkDeviceSize size, VkBufferUsageFlags additionalUsageFlags=0)
: Buffer(size, data, size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | additionalUsageFlags, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0) {}
};
vertexBuffers[0] = make_unique<SimulationBuffer>(vertices.data(), vertices.size() * sizeof(Vertex), // cpu: erase vector element
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); }
vertexBuffers[1] = make_unique<SimulationBuffer>(vertices.data(), vertices.size() * sizeof(Vertex),
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
faceBuffer = make_unique<SimulationBuffer>(faces.data(), faces.size() * sizeof(Face), VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
edgeBuffer = make_unique<SimulationBuffer>(constraintData.edges.data(), constraintData.edges.size() * sizeof(Edge));
triangleBuffer = make_unique<SimulationBuffer>(constraintData.triangles.data(), constraintData.triangles.size() * sizeof(Triangle));
tetrahedronBuffer = make_unique<SimulationBuffer>(constraintData.tetrahedra.data(), constraintData.tetrahedra.size() * sizeof(Tetrahedron));
vertexBuffers[0]->setName("Vertices 0"); void Application::updateConstraintBuffers(VkCommandBuffer commandBuffer) {
vertexBuffers[1]->setName("Vertices 1"); /*
faceBuffer->setName("Faces"); edgeBuffer = make_unique<Buffer>(constraintData.edges.size() * sizeof(Edge), constraintData.edges.data());
edgeBuffer->setName("Edges"); tetrahedronBuffer = make_unique<Buffer>(constraintData.tetrahedra.size() * sizeof(Tetrahedron), constraintData.tetrahedra.data());
triangleBuffer->setName("Triangles");
tetrahedronBuffer->setName("Tetrahedra"); edgeBuffer.value()->setName("Edges");
// triangleBuffer->setName("Triangles");
tetrahedronBuffer.value()->setName("Tetrahedra");
*/
} }
void Application::createComputePipelines() { void Application::createComputePipelines() {
@ -497,6 +512,8 @@ void Application::recordDrawCommands(VkCommandBuffer commandBuffer) {
} }
void Application::update() { void Application::update() {
bufferWriteMutex.lock();
VkCommandBufferBeginInfo beginInfo {}; VkCommandBufferBeginInfo beginInfo {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = 0; beginInfo.flags = 0;
@ -549,7 +566,7 @@ void Application::update() {
submit.pWaitDstStageMask = &waitStage; submit.pWaitDstStageMask = &waitStage;
submitMutex.lock(); submitMutex.lock();
vkQueueSubmit(Instance::instance->computeAndTransferQueue, 1, &submit, transferFence->handle); vkQueueSubmit(Instance::instance->computeAndTransferQueue, 1, &submit, computeTransferredFence->handle);
submitMutex.unlock(); submitMutex.unlock();
} }
@ -558,11 +575,13 @@ void Application::update() {
currentDrawVertexBuffer = 1 - currentDrawVertexBuffer; currentDrawVertexBuffer = 1 - currentDrawVertexBuffer;
vkWaitForFences(Instance::GetDevice(), 1, &transferFence->handle, VK_TRUE, UINT64_MAX); vkWaitForFences(Instance::GetDevice(), 1, &computeTransferredFence->handle, VK_TRUE, UINT64_MAX);
vkResetFences(Instance::GetDevice(), 1, &transferFence->handle); vkResetFences(Instance::GetDevice(), 1, &computeTransferredFence->handle);
currentDrawVertexBuffer = 1 - currentDrawVertexBuffer; currentDrawVertexBuffer = 1 - currentDrawVertexBuffer;
// descriptorPool->bindBuffer(*vertexBuffers[1 - currentDrawVertexBuffer], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DescriptorSet::MESH, 0); // descriptorPool->bindBuffer(*vertexBuffers[1 - currentDrawVertexBuffer], VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, DescriptorSet::MESH, 0);
bufferWriteMutex.unlock();
} }
uint32_t Application::GetGroupCount(uint32_t threads, uint32_t blockSize) { uint32_t Application::GetGroupCount(uint32_t threads, uint32_t blockSize) {
@ -584,7 +603,7 @@ void Application::recordGrabCommands(VkCommandBuffer commandBuffer) {
pushConstants.screenPosition = grabber->previousCursorPosition; pushConstants.screenPosition = grabber->previousCursorPosition;
vkCmdPushConstants(commandBuffer, grabPipeline->layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(GrabPushData), &pushConstants); vkCmdPushConstants(commandBuffer, grabPipeline->layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(GrabPushData), &pushConstants);
uint32_t faceInvocations = GetGroupCount(faceBuffer->size / sizeof(Face), BLOCK_SIZE_GRAB); uint32_t faceInvocations = GetGroupCount(sizeInformation->faceCount, BLOCK_SIZE_GRAB);
vkCmdDispatch(commandBuffer, faceInvocations, 1, 1); vkCmdDispatch(commandBuffer, faceInvocations, 1, 1);
computePipelineBarrier(commandBuffer); computePipelineBarrier(commandBuffer);
@ -616,7 +635,7 @@ void Application::recordGrabCommands(VkCommandBuffer commandBuffer) {
} }
void Application::recordPBDCommands(VkCommandBuffer commandBuffer) { void Application::recordPBDCommands(VkCommandBuffer commandBuffer) {
uint32_t vertexGroupCount = GetGroupCount(vertexBuffers[1 - currentDrawVertexBuffer]->size / sizeof(Vertex), BLOCK_SIZE_PBD); uint32_t vertexGroupCount = GetGroupCount(sizeInformation->vertexCount, BLOCK_SIZE_PBD);
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pbdPipeline->handle); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pbdPipeline->handle);
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pbdPipeline->layout, 0, 1, &descriptorPool->sets[DescriptorSet::MESH], 0, nullptr); vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pbdPipeline->layout, 0, 1, &descriptorPool->sets[DescriptorSet::MESH], 0, nullptr);
@ -661,8 +680,8 @@ void Application::recordPBDCommands(VkCommandBuffer commandBuffer) {
} }
void Application::recordNormalCommands(VkCommandBuffer commandBuffer) { void Application::recordNormalCommands(VkCommandBuffer commandBuffer) {
uint32_t vertexGroupCount = GetGroupCount(vertexBuffers[1 - currentDrawVertexBuffer]->size / sizeof(Vertex), BLOCK_SIZE_NORMAL); uint32_t vertexGroupCount = GetGroupCount(sizeInformation->vertexCount, BLOCK_SIZE_NORMAL);
uint32_t faceGroupCount = GetGroupCount(faceBuffer->size / sizeof(Face), BLOCK_SIZE_NORMAL); uint32_t faceGroupCount = GetGroupCount(sizeInformation->faceCount, BLOCK_SIZE_NORMAL);
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, normalPipeline->handle); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, normalPipeline->handle);
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, normalPipeline->layout, 0, 1, &descriptorPool->sets[DescriptorSet::MESH], 0, nullptr); vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, normalPipeline->layout, 0, 1, &descriptorPool->sets[DescriptorSet::MESH], 0, nullptr);
@ -712,15 +731,22 @@ void Application::imGuiWindows() {
ImGui_ImplGlfw_NewFrame(); ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame(); ImGui::NewFrame();
ImGui::Begin("Performance"); ImGui::Begin("Performance"); {
float updateMS = performanceInformation.updateDuration * 1000.f; float updateMS = performanceInformation.updateDuration * 1000.f;
float updateHZ = 1 / performanceInformation.recentTotalUpdateDurations.average(); float updateHZ = 1 / performanceInformation.recentTotalUpdateDurations.average();
ImGui::Text("Updates: %.0fms | %.1fHz", updateMS, updateHZ); ImGui::Text("Updates: %2.0fms | %.1fHz", updateMS, updateHZ);
float frameMS = performanceInformation.recentFrameDurations.average() * 1000.f; float frameMS = performanceInformation.recentFrameDurations.average() * 1000.f;
float frameHZ = 1 / performanceInformation.recentFrameDurations.average(); float frameHZ = 1 / performanceInformation.recentFrameDurations.average();
ImGui::Text("Frames: %.2fms | %.1fHz", frameMS, frameHZ); ImGui::Text("Frames: %.2fms | %.1fHz", frameMS, frameHZ);
} ImGui::End();
ImGui::End(); ImGui::Begin("Scene"); {
if (ImGui::Button("Add")){
addSoftBody("models/bunny_medium.ply");
}
for (const auto &softBody: softBodies){
ImGui::Text("Some softbody");
}
} ImGui::End();
} }

@ -10,6 +10,39 @@ void ConstraintData::writePartitionInformation() {
tetrahedronPartitions.emplace_back(prePartitionTetrahedronCount, tetrahedra.size() - prePartitionTetrahedronCount); tetrahedronPartitions.emplace_back(prePartitionTetrahedronCount, tetrahedra.size() - prePartitionTetrahedronCount);
} }
void ConstraintData::insert(const ConstraintData &other) {
if (other.partitionCount > partitionCount){
edgePartitions.resize(other.partitionCount);
tetrahedronPartitions.resize(other.partitionCount);
}
// insert constraints, increase partition offsets and sizes
for (size_t i = 0; i < other.partitionCount; i++){
edgePartitions[i].offset += other.edgePartitions[i].offset;
tetrahedronPartitions[i].offset += other.tetrahedronPartitions[i].offset;
auto baseEdgeInsert = other.edges.begin() + other.edgePartitions[i].offset;
edges.insert(edges.begin() + edgePartitions[i].offset + edgePartitions[i].size,
baseEdgeInsert, baseEdgeInsert + other.edgePartitions[i].size);
auto baseTetrahedronInsert = other.tetrahedra.begin() + other.tetrahedronPartitions[i].offset;
tetrahedra.insert(tetrahedra.begin() + tetrahedronPartitions[i].offset + tetrahedronPartitions[i].size,
baseTetrahedronInsert, baseTetrahedronInsert + other.tetrahedronPartitions[i].size);
edgePartitions[i].size += other.edgePartitions[i].size;
tetrahedronPartitions[i].size += other.tetrahedronPartitions[i].size;
}
// increase offsets for remaining partitions
for (size_t i = other.partitionCount; i < partitionCount; i++){
edgePartitions[i].offset += other.edgePartitions[other.partitionCount - 1].offset;
tetrahedronPartitions[i].offset += other.tetrahedronPartitions[other.partitionCount - 1].offset;
}
partitionCount = std::max(partitionCount, other.partitionCount);
}
void DistanceConstraint::writeData(ConstraintData &dataLists) const { void DistanceConstraint::writeData(ConstraintData &dataLists) const {
dataLists.edges.push_back(Edge(a, b, length, compliance)); dataLists.edges.push_back(Edge(a, b, length, compliance));
} }

@ -112,7 +112,7 @@ SoftBody::SoftBody(Mesh* mesh, float edgeCompliance, float triangleCompliance, f
splitConstraints(); splitConstraints();
} }
void SoftBody::applyVertexOffset(const glm::vec3 &offset) { void SoftBody::applyVertexWorldOffset(const glm::vec3 &offset) {
for (Vertex& vertex : vertices){ for (Vertex& vertex : vertices){
vertex.position += offset; vertex.position += offset;
} }

@ -6,40 +6,27 @@
#include "vulkan/image.hpp" #include "vulkan/image.hpp"
#include "vk_mem_alloc.h" #include "vk_mem_alloc.h"
Buffer::Buffer(VkDeviceSize bufferSize, VkBufferUsageFlags bufferUsage, VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags vmaAllocationFlags) : size(bufferSize) { Buffer::Buffer(VkDeviceSize bufferSize, VkBufferUsageFlags bufferUsageFlags, VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags)
: size(bufferSize), bufferUsageFlags(bufferUsageFlags), memoryUsage(memoryUsage), allocationCreateFlags(allocationFlags) {
VkBufferCreateInfo bufferCreateInfo {}; VkBufferCreateInfo bufferCreateInfo {};
bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferCreateInfo.size = bufferSize; bufferCreateInfo.size = bufferSize;
bufferCreateInfo.usage = bufferUsage; bufferCreateInfo.usage = bufferUsageFlags;
bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VmaAllocationCreateInfo allocationCreateInfo {}; VmaAllocationCreateInfo allocationCreateInfo {};
allocationCreateInfo.usage = memoryUsage; allocationCreateInfo.usage = memoryUsage;
allocationCreateInfo.flags = vmaAllocationFlags; allocationCreateInfo.flags = allocationFlags;
vmaCreateBuffer(Instance::GetAllocator(), &bufferCreateInfo, &allocationCreateInfo, &handle, &allocation, &allocationInfo); vmaCreateBuffer(Instance::GetAllocator(), &bufferCreateInfo, &allocationCreateInfo, &handle, &allocation, &allocationInfo);
} }
Buffer::Buffer(VkDeviceSize bufferSize, void *initialData, VkDeviceSize initialDataSize, VkBufferUsageFlags bufferUsage,
VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags vmaAllocationFlags)
: Buffer(bufferSize, bufferUsage | VK_BUFFER_USAGE_TRANSFER_DST_BIT, memoryUsage, vmaAllocationFlags){
Buffer stagingBuffer(
bufferSize,
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VMA_MEMORY_USAGE_AUTO,
VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT
);
memcpy(stagingBuffer.allocationInfo.pMappedData, initialData, initialDataSize);
stagingBuffer.copyTo(this);
}
Buffer::~Buffer() { Buffer::~Buffer() {
vmaDestroyBuffer(Instance::GetAllocator(), handle, allocation); vmaDestroyBuffer(Instance::GetAllocator(), handle, allocation);
} }
void Buffer::copyTo(Buffer *buffer) { void Buffer::copyTo(Buffer *buffer) const {
VkQueue queue = Instance::instance->graphicsAndPresentQueue; VkQueue queue = Instance::instance->graphicsAndPresentQueue;
CommandPool* commandPool = Instance::instance->renderingCommandPool; CommandPool* commandPool = Instance::instance->renderingCommandPool;
@ -52,7 +39,7 @@ void Buffer::copyTo(Buffer *buffer) {
commandPool->endSingleTimeCommandBuffer(commandBuffer, queue); commandPool->endSingleTimeCommandBuffer(commandBuffer, queue);
} }
void Buffer::copyTo(Image *image) { void Buffer::copyTo(Image *image) const {
VkQueue queue = Instance::instance->graphicsAndPresentQueue; VkQueue queue = Instance::instance->graphicsAndPresentQueue;
CommandPool* commandPool = Instance::instance->renderingCommandPool; CommandPool* commandPool = Instance::instance->renderingCommandPool;
@ -74,6 +61,101 @@ void Buffer::copyTo(Image *image) {
commandPool->endSingleTimeCommandBuffer(commandBuffer, queue); commandPool->endSingleTimeCommandBuffer(commandBuffer, queue);
} }
void Buffer::setName(const std::string &name) { void Buffer::setData(void *data, VkDeviceSize offset, VkDeviceSize dataSize, VkCommandBuffer commandBuffer) {
vmaSetAllocationName(Instance::GetAllocator(), allocation, name.data()); if (allocationInfo.pMappedData){
memcpy(reinterpret_cast<u_char*>(allocationInfo.pMappedData) + offset, data, dataSize);
VkMemoryPropertyFlags flags;
vmaGetAllocationMemoryProperties(Instance::GetAllocator(), allocation, &flags);
assert(flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
} else {
bool singleShot = false;
if (!commandBuffer){
commandBuffer = Instance::instance->renderingCommandPool->beginSingleTimeCommandBuffer();
singleShot = true;
}
auto stagingBuffer = getStagingBuffer();
stagingBuffer->setData(data, offset, dataSize);
VkBufferMemoryBarrier bufferMemoryBarrier {};
bufferMemoryBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
bufferMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
bufferMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
bufferMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
bufferMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
bufferMemoryBarrier.buffer = stagingBuffer->handle;
bufferMemoryBarrier.size = dataSize;
bufferMemoryBarrier.offset = offset;
vkCmdPipelineBarrier(
commandBuffer,
VK_PIPELINE_STAGE_HOST_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0,
0, nullptr,
1, &bufferMemoryBarrier,
0, nullptr);
VkBufferCopy region {};
region.srcOffset = offset;
region.dstOffset = offset;
region.size = dataSize;
vkCmdCopyBuffer(commandBuffer, stagingBuffer->handle, handle, 1, &region);
if (singleShot)
Instance::instance->renderingCommandPool->endSingleTimeCommandBuffer(commandBuffer, Instance::instance->graphicsAndPresentQueue);
}
}
void Buffer::setName(const std::string &newName) {
name = newName;
vmaSetAllocationName(Instance::GetAllocator(), allocation, newName.data());
}
unique_ptr<Buffer> Buffer::appended(void *data, VkDeviceSize appendSize, VkCommandBuffer commandBuffer) const {
auto buffer = make_unique<Buffer>(size + appendSize,
bufferUsageFlags,
memoryUsage,
allocationCreateFlags);
buffer->setName(name);
VkBufferCopy copyRegion {};
copyRegion.size = size;
copyRegion.srcOffset = 0;
copyRegion.dstOffset = 0;
vkCmdCopyBuffer(commandBuffer, handle, buffer->handle, 1, &copyRegion);
buffer->setData(data, size, appendSize, commandBuffer);
return buffer;
}
unique_ptr<Buffer> Buffer::replaced(void *data, VkDeviceSize replaceSize, VkCommandBuffer commandBuffer) const {
auto buffer = make_unique<Buffer>(replaceSize,
bufferUsageFlags,
memoryUsage,
allocationCreateFlags);
buffer->setName(name);
buffer->setData(data, 0, replaceSize, commandBuffer);
return buffer;
}
shared_ptr<Buffer> Buffer::getStagingBuffer() {
if (stagingBufferOptional.has_value())
return stagingBufferOptional.value();
stagingBufferOptional = make_shared<Buffer>(
size,
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VMA_MEMORY_USAGE_AUTO,
VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT);
stagingBufferOptional.value()->setName(name + " staging");
return stagingBufferOptional.value();
} }

@ -31,7 +31,9 @@ Image::Image(uint32_t width, uint32_t height, VkFormat format, VkImageTiling til
Image::Image(void *initialData, VkDeviceSize size, uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, Image::Image(void *initialData, VkDeviceSize size, uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling,
VkImageUsageFlags usage) : Image(width, height, format, tiling, usage){ VkImageUsageFlags usage) : Image(width, height, format, tiling, usage){
Buffer stagingBuffer(size, initialData, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0); Buffer stagingBuffer(size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT);
stagingBuffer.setData(initialData, 0, size);
transitionLayout(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); transitionLayout(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
stagingBuffer.copyTo(this); stagingBuffer.copyTo(this);

@ -71,7 +71,21 @@ void Instance::initWindow() {
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE); glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
int monitorCount;
GLFWmonitor** monitors = glfwGetMonitors(&monitorCount);
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
for (int i = 1; i < monitorCount; i++){
int w, h;
glfwGetMonitorPhysicalSize(monitors[i], &w, &h);
if (w > h){
monitor = monitors[i];
break;
}
}
int x, y;
glfwGetMonitorPos(monitor, &x, &y);
window = glfwCreateWindow(800, 600, "Vulkan Simulation", nullptr, nullptr); window = glfwCreateWindow(800, 600, "Vulkan Simulation", nullptr, nullptr);
glfwSetWindowPos(window, x, y);
glfwSetFramebufferSizeCallback(window, [](GLFWwindow* window, int width, int height) { glfwSetFramebufferSizeCallback(window, [](GLFWwindow* window, int width, int height) {
instance->windowResized = true; instance->windowResized = true;
}); });

Loading…
Cancel
Save