Compare commits

..

No commits in common. '0cfc229226cae5e88eb1f97dd43e1a4f4cf5b2bb' and '3dde0fee204e68573823f1d061c5b2c2322eff5d' have entirely different histories.

  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. 292
      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,24 +30,16 @@ Collapsed=0
[Window][Performance] [Window][Performance]
Pos=1716,2 Pos=1716,2
Size=203,345 Size=203,1002
Collapsed=0 Collapsed=0
DockId=0x00000002,0 DockId=0x00000001,0
[Window][Performance 2] [Window][Performance 2]
Pos=1617,2 Pos=1617,2
Size=302,1002 Size=302,1002
Collapsed=0 Collapsed=0
DockId=0x00000002,1 DockId=0x00000001,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 Split=Y Selected=0x60B79D0E DockNode ID=0x00000001 Pos=1716,2 Size=203,1002 Selected=0x60B79D0E
DockNode ID=0x00000002 Parent=0x00000001 SizeRef=203,345 Selected=0x60B79D0E
DockNode ID=0x00000004 Parent=0x00000001 SizeRef=203,655 Selected=0xE192E354

@ -23,14 +23,12 @@
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;
@ -39,7 +37,6 @@ class Semaphore;
class Camera; class Camera;
class DescriptorPool; class DescriptorPool;
class Grabber; class Grabber;
struct SizesUniformData;
class Application { class Application {
public: public:
@ -64,9 +61,8 @@ 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> computeTransferredFence; unique_ptr<Fence> transferFence;
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;
@ -79,10 +75,7 @@ private:
unique_ptr<Grabber> grabber; unique_ptr<Grabber> grabber;
unique_ptr<Buffer> grabBuffer; unique_ptr<Buffer> grabBuffer;
void addSoftBody(const std::string& modelFile, size_t count=1); void createMeshBuffers();
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;
@ -93,7 +86,6 @@ 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,7 +51,6 @@ 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 applyVertexWorldOffset(const glm::vec3& offset); void applyVertexOffset(const glm::vec3& offset);
SoftBody& operator =(const SoftBody& other) = delete; SoftBody& operator =(const SoftBody& other) = delete;
private: private:

@ -4,43 +4,27 @@
#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:
Buffer(VkDeviceSize bufferSize, VkBufferUsageFlags bufferUsage, explicit 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);
unique_ptr<Buffer> appended(void* data, VkDeviceSize appendSize, VkCommandBuffer commandBuffer) const; ~Buffer();
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& newName); void setName(const std::string& name);
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;
private: void copyTo(Buffer* buffer);
optional<shared_ptr<Buffer>> stagingBufferOptional; void copyTo(Image* image);
std::string name;
VkBufferUsageFlags bufferUsageFlags;
VmaMemoryUsage memoryUsage;
VmaAllocationCreateFlags allocationCreateFlags;
}; };

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

@ -10,39 +10,6 @@ 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::applyVertexWorldOffset(const glm::vec3 &offset) { void SoftBody::applyVertexOffset(const glm::vec3 &offset) {
for (Vertex& vertex : vertices){ for (Vertex& vertex : vertices){
vertex.position += offset; vertex.position += offset;
} }

@ -6,27 +6,40 @@
#include "vulkan/image.hpp" #include "vulkan/image.hpp"
#include "vk_mem_alloc.h" #include "vk_mem_alloc.h"
Buffer::Buffer(VkDeviceSize bufferSize, VkBufferUsageFlags bufferUsageFlags, VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags) Buffer::Buffer(VkDeviceSize bufferSize, VkBufferUsageFlags bufferUsage, VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags vmaAllocationFlags) : size(bufferSize) {
: 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 = bufferUsageFlags; bufferCreateInfo.usage = bufferUsage;
bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VmaAllocationCreateInfo allocationCreateInfo {}; VmaAllocationCreateInfo allocationCreateInfo {};
allocationCreateInfo.usage = memoryUsage; allocationCreateInfo.usage = memoryUsage;
allocationCreateInfo.flags = allocationFlags; allocationCreateInfo.flags = vmaAllocationFlags;
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) const { void Buffer::copyTo(Buffer *buffer) {
VkQueue queue = Instance::instance->graphicsAndPresentQueue; VkQueue queue = Instance::instance->graphicsAndPresentQueue;
CommandPool* commandPool = Instance::instance->renderingCommandPool; CommandPool* commandPool = Instance::instance->renderingCommandPool;
@ -39,7 +52,7 @@ void Buffer::copyTo(Buffer *buffer) const {
commandPool->endSingleTimeCommandBuffer(commandBuffer, queue); commandPool->endSingleTimeCommandBuffer(commandBuffer, queue);
} }
void Buffer::copyTo(Image *image) const { void Buffer::copyTo(Image *image) {
VkQueue queue = Instance::instance->graphicsAndPresentQueue; VkQueue queue = Instance::instance->graphicsAndPresentQueue;
CommandPool* commandPool = Instance::instance->renderingCommandPool; CommandPool* commandPool = Instance::instance->renderingCommandPool;
@ -61,101 +74,6 @@ void Buffer::copyTo(Image *image) const {
commandPool->endSingleTimeCommandBuffer(commandBuffer, queue); commandPool->endSingleTimeCommandBuffer(commandBuffer, queue);
} }
void Buffer::setData(void *data, VkDeviceSize offset, VkDeviceSize dataSize, VkCommandBuffer commandBuffer) { void Buffer::setName(const std::string &name) {
if (allocationInfo.pMappedData){ vmaSetAllocationName(Instance::GetAllocator(), allocation, name.data());
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,9 +31,7 @@ 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, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, Buffer stagingBuffer(size, initialData, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0);
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,21 +71,7 @@ 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