Finally get something to show on screen

This commit is contained in:
Bailey Harrison 2022-10-20 20:28:51 +01:00
parent 4d57244151
commit 92ff208c35
3 changed files with 331 additions and 12 deletions

View File

@ -45,10 +45,15 @@ namespace engine {
// submit command lists and draw to the screen // submit command lists and draw to the screen
void draw(); void draw();
void createPipeline(const char* vertShaderPath, const char* fragShaderPath);
bool createBuffer(const gfx::BufferDesc& desc, const void* data, gfx::BufferHandle* out); bool createBuffer(const gfx::BufferDesc& desc, const void* data, gfx::BufferHandle* out);
// waits for the gpu to stop doing stuff to allow for a safe cleanup
void waitIdle();
private: private:
class Impl; struct Impl;
std::unique_ptr<Impl> pimpl; std::unique_ptr<Impl> pimpl;
}; };

View File

@ -2,6 +2,7 @@
#include "window.hpp" #include "window.hpp"
#include "gfx_device.hpp" #include "gfx_device.hpp"
#include "resource_manager.hpp"
namespace engine { namespace engine {
@ -9,6 +10,10 @@ namespace engine {
{ {
m_win = std::make_unique<Window>(appName, false); m_win = std::make_unique<Window>(appName, false);
m_gfx = std::make_unique<GFXDevice>(appName, appVersion, m_win->getHandle()); m_gfx = std::make_unique<GFXDevice>(appName, appVersion, m_win->getHandle());
engine::ResourceManager resMan{};
m_gfx->createPipeline(resMan.getFilePath("shader.vert.spv").string().c_str(), resMan.getFilePath("shader.frag.spv").string().c_str());
} }
Application::~Application() Application::~Application()
@ -19,7 +24,7 @@ namespace engine {
void Application::gameLoop() void Application::gameLoop()
{ {
uint64_t lastTick = m_win->getNanos(); uint64_t lastTick = m_win->getNanos();
constexpr int TICKFREQ = 20; // in hz constexpr int TICKFREQ = 1; // in hz
// single-threaded game loop // single-threaded game loop
while (m_win->isRunning()) { while (m_win->isRunning()) {
@ -30,6 +35,7 @@ namespace engine {
lastTick = m_win->getLastFrameStamp(); lastTick = m_win->getLastFrameStamp();
// do tick stuff here // do tick stuff here
m_win->setTitle("frame time: " + std::to_string(m_win->dt() * 1000.0f) + " ms");
} }
@ -47,6 +53,8 @@ namespace engine {
m_win->getInputAndEvents(); m_win->getInputAndEvents();
} }
m_gfx->waitIdle();
} }
} }

View File

@ -22,10 +22,12 @@
#include <assert.h> #include <assert.h>
#include <unordered_set> #include <unordered_set>
#include <array> #include <array>
#include <fstream>
#include <filesystem>
namespace engine { namespace engine {
// structures // structures and enums
struct LayerInfo { struct LayerInfo {
std::vector<VkLayerProperties> layersAvailable{}; std::vector<VkLayerProperties> layersAvailable{};
@ -61,12 +63,14 @@ namespace engine {
VkRenderPass renderpass; VkRenderPass renderpass;
uint32_t swapchainImageIndex = 0; VkSemaphore acquireSemaphore = VK_NULL_HANDLE; // waits until the image is available
VkSemaphore acquireSemaphore = VK_NULL_HANDLE; VkSemaphore releaseSemaphore = VK_NULL_HANDLE; // waits until rendering finishes
VkSemaphore releaseSemaphore = VK_NULL_HANDLE;
}; };
// enums struct Pipeline {
VkPipelineLayout layout = VK_NULL_HANDLE;
VkPipeline handle = VK_NULL_HANDLE;
};
enum class QueueFlags : uint32_t { enum class QueueFlags : uint32_t {
GRAPHICS = (1 << 0), GRAPHICS = (1 << 0),
@ -74,8 +78,23 @@ namespace engine {
COMPUTE = (1 << 2), COMPUTE = (1 << 2),
}; };
// functions // functions
static std::vector<char> readFile(const std::string& filename)
{
std::ifstream file(filename, std::ios::ate | std::ios::binary);
if (file.is_open() == false) {
throw std::runtime_error("Unable to open file " + filename);
}
std::vector<char> buffer(file.tellg());
file.seekg(0);
file.read(buffer.data(), buffer.size());
file.close();
return buffer;
}
static std::vector<const char*> getRequiredVulkanExtensions(SDL_Window* window) static std::vector<const char*> getRequiredVulkanExtensions(SDL_Window* window)
{ {
SDL_bool res; SDL_bool res;
@ -251,7 +270,7 @@ namespace engine {
for (const auto& presMode : supportDetails.presentModes) { for (const auto& presMode : supportDetails.presentModes) {
if (presMode == VK_PRESENT_MODE_MAILBOX_KHR) { if (presMode == VK_PRESENT_MODE_MAILBOX_KHR) {
swapchain->presentMode = presMode; // this mode allows uncapped FPS while also avoiding screen tearing //swapchain->presentMode = presMode; // this mode allows uncapped FPS while also avoiding screen tearing
} }
} }
@ -348,6 +367,14 @@ namespace engine {
subpass.colorAttachmentCount = 1; subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef; subpass.pColorAttachments = &colorAttachmentRef;
VkSubpassDependency dependency{};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.dstSubpass = 0;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.srcAccessMask = 0;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
VkRenderPassCreateInfo createInfo{}; VkRenderPassCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
createInfo.attachmentCount = 1; createInfo.attachmentCount = 1;
@ -355,6 +382,9 @@ namespace engine {
createInfo.subpassCount = 1; createInfo.subpassCount = 1;
createInfo.pSubpasses = &subpass; createInfo.pSubpasses = &subpass;
createInfo.dependencyCount = 1;
createInfo.pDependencies = &dependency;
//if (swapchain->renderpass != VK_NULL_HANDLE) { //if (swapchain->renderpass != VK_NULL_HANDLE) {
// vkDestroyRenderPass(device, swapchain->renderpass, nullptr); // vkDestroyRenderPass(device, swapchain->renderpass, nullptr);
// swapchain->renderpass = VK_NULL_HANDLE; // swapchain->renderpass = VK_NULL_HANDLE;
@ -420,6 +450,25 @@ namespace engine {
} }
static VkShaderModule createShaderModule(VkDevice device, const std::vector<char>& code)
{
VkShaderModuleCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
createInfo.codeSize = code.size();
createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data());
VkShaderModule shaderModule;
if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
throw std::runtime_error("failed to create shader module!");
}
return shaderModule;
}
// class definitions
struct GFXDevice::Impl { struct GFXDevice::Impl {
VkInstance instance = VK_NULL_HANDLE; VkInstance instance = VK_NULL_HANDLE;
@ -432,6 +481,7 @@ namespace engine {
VkDevice device = VK_NULL_HANDLE; VkDevice device = VK_NULL_HANDLE;
std::vector<Queue> queues{}; std::vector<Queue> queues{};
Queue gfxQueue;
VkCommandPool commandPool = VK_NULL_HANDLE; VkCommandPool commandPool = VK_NULL_HANDLE;
VkCommandBuffer commandBuffer = VK_NULL_HANDLE; VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
@ -439,6 +489,10 @@ namespace engine {
Swapchain swapchain{}; Swapchain swapchain{};
VkFence inFlightFence = VK_NULL_HANDLE;
Pipeline pipeline{};
}; };
GFXDevice::GFXDevice(const char* appName, const char* appVersion, SDL_Window* window) GFXDevice::GFXDevice(const char* appName, const char* appVersion, SDL_Window* window)
@ -792,12 +846,12 @@ namespace engine {
vkGetDeviceQueue(pimpl->device, q.familyIndex, q.queueIndex, &q.handle); vkGetDeviceQueue(pimpl->device, q.familyIndex, q.queueIndex, &q.handle);
} }
Queue gfxQueue = getQueueSupporting(pimpl->queues, QueueFlags::GRAPHICS); pimpl->gfxQueue = getQueueSupporting(pimpl->queues, QueueFlags::GRAPHICS);
VkCommandPoolCreateInfo gfxCmdPoolInfo{ VkCommandPoolCreateInfo gfxCmdPoolInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = gfxQueue.familyIndex, .queueFamilyIndex = pimpl->gfxQueue.familyIndex,
}; };
res = vkCreateCommandPool(pimpl->device, &gfxCmdPoolInfo, nullptr, &pimpl->commandPool); res = vkCreateCommandPool(pimpl->device, &gfxCmdPoolInfo, nullptr, &pimpl->commandPool);
@ -870,12 +924,28 @@ namespace engine {
// Now make the swapchain // Now make the swapchain
createSwapchain(pimpl->device, pimpl->queues, window, pimpl->surface, pimpl->swapchainSupportDetails, &pimpl->swapchain); createSwapchain(pimpl->device, pimpl->queues, window, pimpl->surface, pimpl->swapchainSupportDetails, &pimpl->swapchain);
// for testing purposes, create a pipeline with a simple shader
VkFenceCreateInfo fenceInfo{};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
fenceInfo.pNext = nullptr;
res = vkCreateFence(pimpl->device, &fenceInfo, nullptr, &pimpl->inFlightFence);
} }
GFXDevice::~GFXDevice() GFXDevice::~GFXDevice()
{ {
TRACE("Destroying GFXDevice..."); TRACE("Destroying GFXDevice...");
if (pimpl->pipeline.handle != VK_NULL_HANDLE) {
vkDestroyPipeline(pimpl->device, pimpl->pipeline.handle, nullptr);
vkDestroyPipelineLayout(pimpl->device, pimpl->pipeline.layout, nullptr);
}
vkDestroyFence(pimpl->device, pimpl->inFlightFence, nullptr);
vkDestroySemaphore(pimpl->device, pimpl->swapchain.releaseSemaphore, nullptr); vkDestroySemaphore(pimpl->device, pimpl->swapchain.releaseSemaphore, nullptr);
vkDestroySemaphore(pimpl->device, pimpl->swapchain.acquireSemaphore, nullptr); vkDestroySemaphore(pimpl->device, pimpl->swapchain.acquireSemaphore, nullptr);
for (VkImageView view : pimpl->swapchain.imageViews) { for (VkImageView view : pimpl->swapchain.imageViews) {
@ -898,6 +968,237 @@ namespace engine {
void GFXDevice::draw() void GFXDevice::draw()
{ {
VkResult res;
res = vkWaitForFences(pimpl->device, 1, &pimpl->inFlightFence, VK_TRUE, UINT64_MAX);
assert(res == VK_SUCCESS);
res = vkResetFences(pimpl->device, 1, &pimpl->inFlightFence);
assert(res == VK_SUCCESS);
uint32_t imageIndex;
vkAcquireNextImageKHR(pimpl->device, pimpl->swapchain.swapchain, UINT64_MAX, pimpl->swapchain.acquireSemaphore, VK_NULL_HANDLE, &imageIndex);
vkResetCommandBuffer(pimpl->commandBuffer, 0);
// now record command buffer
{
VkCommandBufferBeginInfo beginInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
beginInfo.flags = 0;
beginInfo.pInheritanceInfo = nullptr;
res = vkBeginCommandBuffer(pimpl->commandBuffer, &beginInfo);
assert(res == VK_SUCCESS);
VkRenderPassBeginInfo renderPassInfo{ VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
renderPassInfo.renderPass = pimpl->swapchain.renderpass;
renderPassInfo.framebuffer = pimpl->swapchain.framebuffers[imageIndex];
renderPassInfo.renderArea.offset = { 0, 0 };
renderPassInfo.renderArea.extent = pimpl->swapchain.extent;
VkClearValue clearColor{ {0.0f, 0.0f, 0.0f, 0.0f} };
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;
vkCmdBeginRenderPass(pimpl->commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(pimpl->commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pimpl->pipeline.handle);
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float)pimpl->swapchain.extent.width;
viewport.height = (float)pimpl->swapchain.extent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
vkCmdSetViewport(pimpl->commandBuffer, 0, 1, &viewport);
VkRect2D scissor{};
scissor.offset = { 0, 0 };
scissor.extent = pimpl->swapchain.extent;
vkCmdSetScissor(pimpl->commandBuffer, 0, 1, &scissor);
vkCmdDraw(pimpl->commandBuffer, 3, 1, 0, 0);
vkCmdEndRenderPass(pimpl->commandBuffer);
res = vkEndCommandBuffer(pimpl->commandBuffer);
assert(res == VK_SUCCESS);
}
VkSubmitInfo submitInfo{ VK_STRUCTURE_TYPE_SUBMIT_INFO };
VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = &pimpl->swapchain.acquireSemaphore;
submitInfo.pWaitDstStageMask = waitStages;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &pimpl->commandBuffer;
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = &pimpl->swapchain.releaseSemaphore;
res = vkQueueSubmit(pimpl->gfxQueue.handle, 1, &submitInfo, pimpl->inFlightFence);
assert(res == VK_SUCCESS);
VkPresentInfoKHR presentInfo{ VK_STRUCTURE_TYPE_PRESENT_INFO_KHR };
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = &pimpl->swapchain.releaseSemaphore;
VkSwapchainKHR swapchains[] = { pimpl->swapchain.swapchain };
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapchains;
presentInfo.pImageIndices = &imageIndex;
presentInfo.pResults = nullptr;
res = vkQueuePresentKHR(pimpl->gfxQueue.handle, &presentInfo);
assert(res == VK_SUCCESS);
}
void GFXDevice::createPipeline(const char* vertShaderPath, const char* fragShaderPath)
{
VkResult res;
auto vertShaderCode = readFile(vertShaderPath);
auto fragShaderCode = readFile(fragShaderPath);
INFO("Opened shader: {}", std::filesystem::path(vertShaderPath).filename().string());
VkShaderModule vertShaderModule = createShaderModule(pimpl->device, vertShaderCode);
VkShaderModule fragShaderModule = createShaderModule(pimpl->device, fragShaderCode);
VkPipelineShaderStageCreateInfo vertShaderStageInfo{ VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO };
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertShaderStageInfo.module = vertShaderModule;
vertShaderStageInfo.pName = "main";
vertShaderStageInfo.pSpecializationInfo = nullptr;
VkPipelineShaderStageCreateInfo fragShaderStageInfo{ VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO };
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragShaderStageInfo.module = fragShaderModule;
fragShaderStageInfo.pName = "main";
fragShaderStageInfo.pSpecializationInfo = nullptr;
VkPipelineShaderStageCreateInfo shaderStages[2] = { vertShaderStageInfo, fragShaderStageInfo };
VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 0;
vertexInputInfo.pVertexBindingDescriptions = nullptr;
vertexInputInfo.vertexAttributeDescriptionCount = 0;
vertexInputInfo.pVertexAttributeDescriptions = nullptr;
VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;
VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float)pimpl->swapchain.extent.width;
viewport.height = (float)pimpl->swapchain.extent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor{};
scissor.offset = { 0, 0 };
scissor.extent = pimpl->swapchain.extent;
std::vector<VkDynamicState> dynamicStates = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState{};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = dynamicStates.size();
dynamicState.pDynamicStates = dynamicStates.data();
VkPipelineViewportStateCreateInfo viewportState{};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = 1;
viewportState.pViewports = &viewport;
viewportState.scissorCount = 1;
viewportState.pScissors = &scissor;
VkPipelineRasterizationStateCreateInfo rasterizer{};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
rasterizer.depthBiasConstantFactor = 0.0f; // ignored
rasterizer.depthBiasClamp = 0.0f; // ignored
rasterizer.depthBiasSlopeFactor = 0.0f; // ignored
VkPipelineMultisampleStateCreateInfo multisampling{};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
multisampling.minSampleShading = 1.0f; // ignored
multisampling.pSampleMask = nullptr; // ignored
multisampling.alphaToCoverageEnable = VK_FALSE; // ignored
multisampling.alphaToOneEnable = VK_FALSE; // ignored
VkPipelineColorBlendAttachmentState colorBlendAttachment{};
colorBlendAttachment.colorWriteMask =
VK_COLOR_COMPONENT_R_BIT |
VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT |
VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachment.blendEnable = VK_FALSE;
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // ignored
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // ignored
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; // ignored
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // ignored
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // ignored
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // ignored
VkPipelineColorBlendStateCreateInfo colorBlending{};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_FALSE;
colorBlending.logicOp = VK_LOGIC_OP_COPY; // ignored
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
colorBlending.blendConstants[0] = 0.0f; // ignored
colorBlending.blendConstants[1] = 0.0f; // ignored
colorBlending.blendConstants[2] = 0.0f; // ignored
colorBlending.blendConstants[3] = 0.0f; // ignored
VkPipelineLayoutCreateInfo layoutInfo{};
layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
// everything is 0 because we're not using uniforms
layoutInfo.setLayoutCount = 0;
layoutInfo.pSetLayouts = nullptr;
layoutInfo.pushConstantRangeCount = 0;
layoutInfo.pPushConstantRanges = nullptr;
res = vkCreatePipelineLayout(pimpl->device, &layoutInfo, nullptr, &pimpl->pipeline.layout);
assert(res == VK_SUCCESS);
VkGraphicsPipelineCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
createInfo.stageCount = 2;
createInfo.pStages = shaderStages;
createInfo.pVertexInputState = &vertexInputInfo;
createInfo.pInputAssemblyState = &inputAssembly;
createInfo.pViewportState = &viewportState;
createInfo.pRasterizationState = &rasterizer;
createInfo.pMultisampleState = &multisampling;
createInfo.pDepthStencilState = nullptr;
createInfo.pColorBlendState = &colorBlending;
createInfo.pDynamicState = &dynamicState;
createInfo.layout = pimpl->pipeline.layout;
createInfo.renderPass = pimpl->swapchain.renderpass;
createInfo.subpass = 0;
createInfo.basePipelineHandle = VK_NULL_HANDLE;
createInfo.basePipelineIndex = -1;
res = vkCreateGraphicsPipelines(pimpl->device, VK_NULL_HANDLE, 1, &createInfo, nullptr, &pimpl->pipeline.handle);
assert(res == VK_SUCCESS);
vkDestroyShaderModule(pimpl->device, fragShaderModule, nullptr);
vkDestroyShaderModule(pimpl->device, vertShaderModule, nullptr);
} }
@ -906,6 +1207,11 @@ namespace engine {
return false; return false;
} }
void GFXDevice::waitIdle()
{
vkDeviceWaitIdle(pimpl->device);
}
} }
#endif #endif