1645 lines
46 KiB
C++
1645 lines
46 KiB
C++
/*
|
|
* Vulkan Example base class
|
|
*
|
|
* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de
|
|
*
|
|
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
|
|
*/
|
|
|
|
#include "vulkanexamplebase.h"
|
|
|
|
std::vector<const char*> VulkanExampleBase::args;
|
|
|
|
VkResult VulkanExampleBase::createInstance(bool enableValidation)
|
|
{
|
|
this->enableValidation = enableValidation;
|
|
|
|
VkApplicationInfo appInfo = {};
|
|
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
|
appInfo.pApplicationName = name.c_str();
|
|
appInfo.pEngineName = name.c_str();
|
|
appInfo.apiVersion = VK_API_VERSION_1_0;
|
|
|
|
std::vector<const char*> enabledExtensions = { VK_KHR_SURFACE_EXTENSION_NAME };
|
|
|
|
// Enable surface extensions depending on os
|
|
#if defined(_WIN32)
|
|
enabledExtensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
|
|
#elif defined(__ANDROID__)
|
|
enabledExtensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
|
|
#elif defined(_DIRECT2DISPLAY)
|
|
enabledExtensions.push_back(VK_KHR_DISPLAY_EXTENSION_NAME);
|
|
#elif defined(__linux__)
|
|
enabledExtensions.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME);
|
|
#endif
|
|
|
|
VkInstanceCreateInfo instanceCreateInfo = {};
|
|
instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
|
instanceCreateInfo.pNext = NULL;
|
|
instanceCreateInfo.pApplicationInfo = &appInfo;
|
|
if (enabledExtensions.size() > 0)
|
|
{
|
|
if (enableValidation)
|
|
{
|
|
enabledExtensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
|
|
}
|
|
instanceCreateInfo.enabledExtensionCount = (uint32_t)enabledExtensions.size();
|
|
instanceCreateInfo.ppEnabledExtensionNames = enabledExtensions.data();
|
|
}
|
|
if (enableValidation)
|
|
{
|
|
instanceCreateInfo.enabledLayerCount = vkDebug::validationLayerCount;
|
|
instanceCreateInfo.ppEnabledLayerNames = vkDebug::validationLayerNames;
|
|
}
|
|
return vkCreateInstance(&instanceCreateInfo, nullptr, &instance);
|
|
}
|
|
|
|
std::string VulkanExampleBase::getWindowTitle()
|
|
{
|
|
std::string device(deviceProperties.deviceName);
|
|
std::string windowTitle;
|
|
windowTitle = title + " - " + device;
|
|
if (!enableTextOverlay)
|
|
{
|
|
windowTitle += " - " + std::to_string(frameCounter) + " fps";
|
|
}
|
|
return windowTitle;
|
|
}
|
|
|
|
const std::string VulkanExampleBase::getAssetPath()
|
|
{
|
|
#if defined(__ANDROID__)
|
|
return "";
|
|
#else
|
|
return "./../data/";
|
|
#endif
|
|
}
|
|
|
|
bool VulkanExampleBase::checkCommandBuffers()
|
|
{
|
|
for (auto& cmdBuffer : drawCmdBuffers)
|
|
{
|
|
if (cmdBuffer == VK_NULL_HANDLE)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void VulkanExampleBase::createCommandBuffers()
|
|
{
|
|
// Create one command buffer for each swap chain image and reuse for rendering
|
|
drawCmdBuffers.resize(swapChain.imageCount);
|
|
|
|
VkCommandBufferAllocateInfo cmdBufAllocateInfo =
|
|
vkTools::initializers::commandBufferAllocateInfo(
|
|
cmdPool,
|
|
VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
|
static_cast<uint32_t>(drawCmdBuffers.size()));
|
|
|
|
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, drawCmdBuffers.data()));
|
|
}
|
|
|
|
void VulkanExampleBase::destroyCommandBuffers()
|
|
{
|
|
vkFreeCommandBuffers(device, cmdPool, static_cast<uint32_t>(drawCmdBuffers.size()), drawCmdBuffers.data());
|
|
}
|
|
|
|
VkCommandBuffer VulkanExampleBase::createCommandBuffer(VkCommandBufferLevel level, bool begin)
|
|
{
|
|
VkCommandBuffer cmdBuffer;
|
|
|
|
VkCommandBufferAllocateInfo cmdBufAllocateInfo =
|
|
vkTools::initializers::commandBufferAllocateInfo(
|
|
cmdPool,
|
|
level,
|
|
1);
|
|
|
|
VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &cmdBuffer));
|
|
|
|
// If requested, also start the new command buffer
|
|
if (begin)
|
|
{
|
|
VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo();
|
|
VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuffer, &cmdBufInfo));
|
|
}
|
|
|
|
return cmdBuffer;
|
|
}
|
|
|
|
void VulkanExampleBase::flushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue, bool free)
|
|
{
|
|
if (commandBuffer == VK_NULL_HANDLE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer));
|
|
|
|
VkSubmitInfo submitInfo = {};
|
|
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
|
submitInfo.commandBufferCount = 1;
|
|
submitInfo.pCommandBuffers = &commandBuffer;
|
|
|
|
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
|
|
VK_CHECK_RESULT(vkQueueWaitIdle(queue));
|
|
|
|
if (free)
|
|
{
|
|
vkFreeCommandBuffers(device, cmdPool, 1, &commandBuffer);
|
|
}
|
|
}
|
|
|
|
void VulkanExampleBase::createPipelineCache()
|
|
{
|
|
VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {};
|
|
pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
|
|
VK_CHECK_RESULT(vkCreatePipelineCache(device, &pipelineCacheCreateInfo, nullptr, &pipelineCache));
|
|
}
|
|
|
|
void VulkanExampleBase::prepare()
|
|
{
|
|
if (vulkanDevice->enableDebugMarkers)
|
|
{
|
|
vkDebug::DebugMarker::setup(device);
|
|
}
|
|
createCommandPool();
|
|
setupSwapChain();
|
|
createCommandBuffers();
|
|
setupDepthStencil();
|
|
setupRenderPass();
|
|
createPipelineCache();
|
|
setupFrameBuffer();
|
|
// Create a simple texture loader class
|
|
textureLoader = new vkTools::VulkanTextureLoader(vulkanDevice, queue, cmdPool);
|
|
#if defined(__ANDROID__)
|
|
textureLoader->assetManager = androidApp->activity->assetManager;
|
|
#endif
|
|
if (enableTextOverlay)
|
|
{
|
|
// Load the text rendering shaders
|
|
std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
|
|
shaderStages.push_back(loadShader(getAssetPath() + "shaders/base/textoverlay.vert.spv", VK_SHADER_STAGE_VERTEX_BIT));
|
|
shaderStages.push_back(loadShader(getAssetPath() + "shaders/base/textoverlay.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT));
|
|
textOverlay = new VulkanTextOverlay(
|
|
vulkanDevice,
|
|
queue,
|
|
frameBuffers,
|
|
colorformat,
|
|
depthFormat,
|
|
&width,
|
|
&height,
|
|
shaderStages
|
|
);
|
|
updateTextOverlay();
|
|
}
|
|
}
|
|
|
|
VkPipelineShaderStageCreateInfo VulkanExampleBase::loadShader(std::string fileName, VkShaderStageFlagBits stage)
|
|
{
|
|
VkPipelineShaderStageCreateInfo shaderStage = {};
|
|
shaderStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
shaderStage.stage = stage;
|
|
#if defined(__ANDROID__)
|
|
shaderStage.module = vkTools::loadShader(androidApp->activity->assetManager, fileName.c_str(), device, stage);
|
|
#else
|
|
shaderStage.module = vkTools::loadShader(fileName.c_str(), device, stage);
|
|
#endif
|
|
shaderStage.pName = "main"; // todo : make param
|
|
assert(shaderStage.module != NULL);
|
|
shaderModules.push_back(shaderStage.module);
|
|
return shaderStage;
|
|
}
|
|
|
|
VkBool32 VulkanExampleBase::createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, VkDeviceSize size, void * data, VkBuffer * buffer, VkDeviceMemory * memory)
|
|
{
|
|
VkMemoryRequirements memReqs;
|
|
VkMemoryAllocateInfo memAlloc = vkTools::initializers::memoryAllocateInfo();
|
|
VkBufferCreateInfo bufferCreateInfo = vkTools::initializers::bufferCreateInfo(usageFlags, size);
|
|
|
|
VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, buffer));
|
|
|
|
vkGetBufferMemoryRequirements(device, *buffer, &memReqs);
|
|
memAlloc.allocationSize = memReqs.size;
|
|
memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, memoryPropertyFlags);
|
|
VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, memory));
|
|
if (data != nullptr)
|
|
{
|
|
void *mapped;
|
|
VK_CHECK_RESULT(vkMapMemory(device, *memory, 0, size, 0, &mapped));
|
|
memcpy(mapped, data, size);
|
|
vkUnmapMemory(device, *memory);
|
|
}
|
|
VK_CHECK_RESULT(vkBindBufferMemory(device, *buffer, *memory, 0));
|
|
|
|
return true;
|
|
}
|
|
|
|
VkBool32 VulkanExampleBase::createBuffer(VkBufferUsageFlags usage, VkDeviceSize size, void * data, VkBuffer *buffer, VkDeviceMemory *memory)
|
|
{
|
|
return createBuffer(usage, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, size, data, buffer, memory);
|
|
}
|
|
|
|
VkBool32 VulkanExampleBase::createBuffer(VkBufferUsageFlags usage, VkDeviceSize size, void * data, VkBuffer * buffer, VkDeviceMemory * memory, VkDescriptorBufferInfo * descriptor)
|
|
{
|
|
VkBool32 res = createBuffer(usage, size, data, buffer, memory);
|
|
if (res)
|
|
{
|
|
descriptor->offset = 0;
|
|
descriptor->buffer = *buffer;
|
|
descriptor->range = size;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
VkBool32 VulkanExampleBase::createBuffer(VkBufferUsageFlags usage, VkMemoryPropertyFlags memoryPropertyFlags, VkDeviceSize size, void * data, VkBuffer * buffer, VkDeviceMemory * memory, VkDescriptorBufferInfo * descriptor)
|
|
{
|
|
VkBool32 res = createBuffer(usage, memoryPropertyFlags, size, data, buffer, memory);
|
|
if (res)
|
|
{
|
|
descriptor->offset = 0;
|
|
descriptor->buffer = *buffer;
|
|
descriptor->range = size;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void VulkanExampleBase::loadMesh(std::string filename, vkMeshLoader::MeshBuffer * meshBuffer, std::vector<vkMeshLoader::VertexLayout> vertexLayout, float scale)
|
|
{
|
|
vkMeshLoader::MeshCreateInfo meshCreateInfo;
|
|
meshCreateInfo.scale = glm::vec3(scale);
|
|
meshCreateInfo.center = glm::vec3(0.0f);
|
|
meshCreateInfo.uvscale = glm::vec2(1.0f);
|
|
loadMesh(filename, meshBuffer, vertexLayout, &meshCreateInfo);
|
|
}
|
|
|
|
void VulkanExampleBase::loadMesh(std::string filename, vkMeshLoader::MeshBuffer * meshBuffer, std::vector<vkMeshLoader::VertexLayout> vertexLayout, vkMeshLoader::MeshCreateInfo *meshCreateInfo)
|
|
{
|
|
VulkanMeshLoader *mesh = new VulkanMeshLoader(vulkanDevice);
|
|
|
|
#if defined(__ANDROID__)
|
|
mesh->assetManager = androidApp->activity->assetManager;
|
|
#endif
|
|
|
|
mesh->LoadMesh(filename);
|
|
assert(mesh->m_Entries.size() > 0);
|
|
|
|
VkCommandBuffer copyCmd = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, false);
|
|
|
|
mesh->createBuffers(
|
|
meshBuffer,
|
|
vertexLayout,
|
|
meshCreateInfo,
|
|
true,
|
|
copyCmd,
|
|
queue);
|
|
|
|
vkFreeCommandBuffers(device, cmdPool, 1, ©Cmd);
|
|
|
|
meshBuffer->dim = mesh->dim.size;
|
|
|
|
delete(mesh);
|
|
}
|
|
|
|
void VulkanExampleBase::renderLoop()
|
|
{
|
|
destWidth = width;
|
|
destHeight = height;
|
|
#if defined(_WIN32)
|
|
MSG msg;
|
|
while (TRUE)
|
|
{
|
|
auto tStart = std::chrono::high_resolution_clock::now();
|
|
if (viewUpdated)
|
|
{
|
|
viewUpdated = false;
|
|
viewChanged();
|
|
}
|
|
|
|
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
|
|
if (msg.message == WM_QUIT)
|
|
{
|
|
break;
|
|
}
|
|
|
|
render();
|
|
frameCounter++;
|
|
auto tEnd = std::chrono::high_resolution_clock::now();
|
|
auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
|
|
frameTimer = (float)tDiff / 1000.0f;
|
|
camera.update(frameTimer);
|
|
if (camera.moving())
|
|
{
|
|
viewUpdated = true;
|
|
}
|
|
// Convert to clamped timer value
|
|
if (!paused)
|
|
{
|
|
timer += timerSpeed * frameTimer;
|
|
if (timer > 1.0)
|
|
{
|
|
timer -= 1.0f;
|
|
}
|
|
}
|
|
fpsTimer += (float)tDiff;
|
|
if (fpsTimer > 1000.0f)
|
|
{
|
|
if (!enableTextOverlay)
|
|
{
|
|
std::string windowTitle = getWindowTitle();
|
|
SetWindowText(window, windowTitle.c_str());
|
|
}
|
|
lastFPS = static_cast<uint32_t>(1.0f / frameTimer);
|
|
updateTextOverlay();
|
|
fpsTimer = 0.0f;
|
|
frameCounter = 0;
|
|
}
|
|
}
|
|
#elif defined(__ANDROID__)
|
|
while (1)
|
|
{
|
|
int ident;
|
|
int events;
|
|
struct android_poll_source* source;
|
|
bool destroy = false;
|
|
|
|
focused = true;
|
|
|
|
while ((ident = ALooper_pollAll(focused ? 0 : -1, NULL, &events, (void**)&source)) >= 0)
|
|
{
|
|
if (source != NULL)
|
|
{
|
|
source->process(androidApp, source);
|
|
}
|
|
if (androidApp->destroyRequested != 0)
|
|
{
|
|
LOGD("Android app destroy requested");
|
|
destroy = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// App destruction requested
|
|
// Exit loop, example will be destroyed in application main
|
|
if (destroy)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Render frame
|
|
if (prepared)
|
|
{
|
|
auto tStart = std::chrono::high_resolution_clock::now();
|
|
render();
|
|
frameCounter++;
|
|
auto tEnd = std::chrono::high_resolution_clock::now();
|
|
auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
|
|
frameTimer = tDiff / 1000.0f;
|
|
camera.update(frameTimer);
|
|
// Convert to clamped timer value
|
|
if (!paused)
|
|
{
|
|
timer += timerSpeed * frameTimer;
|
|
if (timer > 1.0)
|
|
{
|
|
timer -= 1.0f;
|
|
}
|
|
}
|
|
fpsTimer += (float)tDiff;
|
|
if (fpsTimer > 1000.0f)
|
|
{
|
|
lastFPS = frameCounter;
|
|
updateTextOverlay();
|
|
fpsTimer = 0.0f;
|
|
frameCounter = 0;
|
|
}
|
|
// Check gamepad state
|
|
const float deadZone = 0.0015f;
|
|
// todo : check if gamepad is present
|
|
// todo : time based and relative axis positions
|
|
bool updateView = false;
|
|
if (camera.type != Camera::CameraType::firstperson)
|
|
{
|
|
// Rotate
|
|
if (std::abs(gamePadState.axisLeft.x) > deadZone)
|
|
{
|
|
rotation.y += gamePadState.axisLeft.x * 0.5f * rotationSpeed;
|
|
camera.rotate(glm::vec3(0.0f, gamePadState.axisLeft.x * 0.5f, 0.0f));
|
|
updateView = true;
|
|
}
|
|
if (std::abs(gamePadState.axisLeft.y) > deadZone)
|
|
{
|
|
rotation.x -= gamePadState.axisLeft.y * 0.5f * rotationSpeed;
|
|
camera.rotate(glm::vec3(gamePadState.axisLeft.y * 0.5f, 0.0f, 0.0f));
|
|
updateView = true;
|
|
}
|
|
// Zoom
|
|
if (std::abs(gamePadState.axisRight.y) > deadZone)
|
|
{
|
|
zoom -= gamePadState.axisRight.y * 0.01f * zoomSpeed;
|
|
updateView = true;
|
|
}
|
|
if (updateView)
|
|
{
|
|
viewChanged();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
updateView = camera.updatePad(gamePadState.axisLeft, gamePadState.axisRight, frameTimer);
|
|
if (updateView)
|
|
{
|
|
viewChanged();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#elif defined(_DIRECT2DISPLAY)
|
|
while (!quit)
|
|
{
|
|
auto tStart = std::chrono::high_resolution_clock::now();
|
|
if (viewUpdated)
|
|
{
|
|
viewUpdated = false;
|
|
viewChanged();
|
|
}
|
|
render();
|
|
frameCounter++;
|
|
auto tEnd = std::chrono::high_resolution_clock::now();
|
|
auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
|
|
frameTimer = tDiff / 1000.0f;
|
|
camera.update(frameTimer);
|
|
if (camera.moving())
|
|
{
|
|
viewUpdated = true;
|
|
}
|
|
// Convert to clamped timer value
|
|
if (!paused)
|
|
{
|
|
timer += timerSpeed * frameTimer;
|
|
if (timer > 1.0)
|
|
{
|
|
timer -= 1.0f;
|
|
}
|
|
}
|
|
fpsTimer += (float)tDiff;
|
|
if (fpsTimer > 1000.0f)
|
|
{
|
|
lastFPS = frameCounter;
|
|
updateTextOverlay();
|
|
fpsTimer = 0.0f;
|
|
frameCounter = 0;
|
|
}
|
|
}
|
|
#elif defined(__linux__)
|
|
xcb_flush(connection);
|
|
while (!quit)
|
|
{
|
|
auto tStart = std::chrono::high_resolution_clock::now();
|
|
if (viewUpdated)
|
|
{
|
|
viewUpdated = false;
|
|
viewChanged();
|
|
}
|
|
xcb_generic_event_t *event;
|
|
while ((event = xcb_poll_for_event(connection)))
|
|
{
|
|
handleEvent(event);
|
|
free(event);
|
|
}
|
|
render();
|
|
frameCounter++;
|
|
auto tEnd = std::chrono::high_resolution_clock::now();
|
|
auto tDiff = std::chrono::duration<double, std::milli>(tEnd - tStart).count();
|
|
frameTimer = tDiff / 1000.0f;
|
|
camera.update(frameTimer);
|
|
if (camera.moving())
|
|
{
|
|
viewUpdated = true;
|
|
}
|
|
// Convert to clamped timer value
|
|
if (!paused)
|
|
{
|
|
timer += timerSpeed * frameTimer;
|
|
if (timer > 1.0)
|
|
{
|
|
timer -= 1.0f;
|
|
}
|
|
}
|
|
fpsTimer += (float)tDiff;
|
|
if (fpsTimer > 1000.0f)
|
|
{
|
|
if (!enableTextOverlay)
|
|
{
|
|
std::string windowTitle = getWindowTitle();
|
|
xcb_change_property(connection, XCB_PROP_MODE_REPLACE,
|
|
window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
|
|
windowTitle.size(), windowTitle.c_str());
|
|
}
|
|
lastFPS = frameCounter;
|
|
updateTextOverlay();
|
|
fpsTimer = 0.0f;
|
|
frameCounter = 0;
|
|
}
|
|
}
|
|
#endif
|
|
// Flush device to make sure all resources can be freed
|
|
vkDeviceWaitIdle(device);
|
|
}
|
|
|
|
void VulkanExampleBase::updateTextOverlay()
|
|
{
|
|
if (!enableTextOverlay)
|
|
return;
|
|
|
|
textOverlay->beginTextUpdate();
|
|
|
|
textOverlay->addText(title, 5.0f, 5.0f, VulkanTextOverlay::alignLeft);
|
|
|
|
std::stringstream ss;
|
|
ss << std::fixed << std::setprecision(3) << (frameTimer * 1000.0f) << "ms (" << lastFPS << " fps)";
|
|
textOverlay->addText(ss.str(), 5.0f, 25.0f, VulkanTextOverlay::alignLeft);
|
|
|
|
textOverlay->addText(deviceProperties.deviceName, 5.0f, 45.0f, VulkanTextOverlay::alignLeft);
|
|
|
|
getOverlayText(textOverlay);
|
|
|
|
textOverlay->endTextUpdate();
|
|
}
|
|
|
|
void VulkanExampleBase::getOverlayText(VulkanTextOverlay *textOverlay)
|
|
{
|
|
// Can be overriden in derived class
|
|
}
|
|
|
|
void VulkanExampleBase::prepareFrame()
|
|
{
|
|
// Acquire the next image from the swap chaing
|
|
VK_CHECK_RESULT(swapChain.acquireNextImage(semaphores.presentComplete, ¤tBuffer));
|
|
}
|
|
|
|
void VulkanExampleBase::submitFrame()
|
|
{
|
|
bool submitTextOverlay = enableTextOverlay && textOverlay->visible;
|
|
|
|
if (submitTextOverlay)
|
|
{
|
|
// Wait for color attachment output to finish before rendering the text overlay
|
|
VkPipelineStageFlags stageFlags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
submitInfo.pWaitDstStageMask = &stageFlags;
|
|
|
|
// Set semaphores
|
|
// Wait for render complete semaphore
|
|
submitInfo.waitSemaphoreCount = 1;
|
|
submitInfo.pWaitSemaphores = &semaphores.renderComplete;
|
|
// Signal ready with text overlay complete semaphpre
|
|
submitInfo.signalSemaphoreCount = 1;
|
|
submitInfo.pSignalSemaphores = &semaphores.textOverlayComplete;
|
|
|
|
// Submit current text overlay command buffer
|
|
submitInfo.commandBufferCount = 1;
|
|
submitInfo.pCommandBuffers = &textOverlay->cmdBuffers[currentBuffer];
|
|
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
|
|
|
|
// Reset stage mask
|
|
submitInfo.pWaitDstStageMask = &submitPipelineStages;
|
|
// Reset wait and signal semaphores for rendering next frame
|
|
// Wait for swap chain presentation to finish
|
|
submitInfo.waitSemaphoreCount = 1;
|
|
submitInfo.pWaitSemaphores = &semaphores.presentComplete;
|
|
// Signal ready with offscreen semaphore
|
|
submitInfo.signalSemaphoreCount = 1;
|
|
submitInfo.pSignalSemaphores = &semaphores.renderComplete;
|
|
}
|
|
|
|
VK_CHECK_RESULT(swapChain.queuePresent(queue, currentBuffer, submitTextOverlay ? semaphores.textOverlayComplete : semaphores.renderComplete));
|
|
|
|
VK_CHECK_RESULT(vkQueueWaitIdle(queue));
|
|
}
|
|
|
|
VulkanExampleBase::VulkanExampleBase(bool enableValidation)
|
|
{
|
|
// Parse command line arguments
|
|
for (auto arg : args)
|
|
{
|
|
if (arg == std::string("-validation"))
|
|
{
|
|
this->enableValidation = true;
|
|
}
|
|
if (arg == std::string("-vsync"))
|
|
{
|
|
enableVSync = true;
|
|
}
|
|
}
|
|
#if defined(__ANDROID__)
|
|
// Vulkan library is loaded dynamically on Android
|
|
bool libLoaded = loadVulkanLibrary();
|
|
assert(libLoaded);
|
|
#elif defined(_DIRECT2DISPLAY)
|
|
|
|
#elif defined(__linux__)
|
|
initxcbConnection();
|
|
#endif
|
|
|
|
#if defined(_WIN32)
|
|
// Enable console if validation is active
|
|
// Debug message callback will output to it
|
|
if (this->enableValidation)
|
|
{
|
|
setupConsole("VulkanExample");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
VulkanExampleBase::~VulkanExampleBase()
|
|
{
|
|
// Clean up Vulkan resources
|
|
swapChain.cleanup();
|
|
if (descriptorPool != VK_NULL_HANDLE)
|
|
{
|
|
vkDestroyDescriptorPool(device, descriptorPool, nullptr);
|
|
}
|
|
destroyCommandBuffers();
|
|
vkDestroyRenderPass(device, renderPass, nullptr);
|
|
for (uint32_t i = 0; i < frameBuffers.size(); i++)
|
|
{
|
|
vkDestroyFramebuffer(device, frameBuffers[i], nullptr);
|
|
}
|
|
|
|
for (auto& shaderModule : shaderModules)
|
|
{
|
|
vkDestroyShaderModule(device, shaderModule, nullptr);
|
|
}
|
|
vkDestroyImageView(device, depthStencil.view, nullptr);
|
|
vkDestroyImage(device, depthStencil.image, nullptr);
|
|
vkFreeMemory(device, depthStencil.mem, nullptr);
|
|
|
|
vkDestroyPipelineCache(device, pipelineCache, nullptr);
|
|
|
|
if (textureLoader)
|
|
{
|
|
delete textureLoader;
|
|
}
|
|
|
|
vkDestroyCommandPool(device, cmdPool, nullptr);
|
|
|
|
vkDestroySemaphore(device, semaphores.presentComplete, nullptr);
|
|
vkDestroySemaphore(device, semaphores.renderComplete, nullptr);
|
|
vkDestroySemaphore(device, semaphores.textOverlayComplete, nullptr);
|
|
|
|
if (enableTextOverlay)
|
|
{
|
|
delete textOverlay;
|
|
}
|
|
|
|
delete vulkanDevice;
|
|
|
|
if (enableValidation)
|
|
{
|
|
vkDebug::freeDebugCallback(instance);
|
|
}
|
|
|
|
vkDestroyInstance(instance, nullptr);
|
|
|
|
#if defined(_DIRECT2DISPLAY)
|
|
|
|
#elif defined(__linux)
|
|
#if defined(__ANDROID__)
|
|
// todo : android cleanup (if required)
|
|
#else
|
|
xcb_destroy_window(connection, window);
|
|
xcb_disconnect(connection);
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
void VulkanExampleBase::initVulkan()
|
|
{
|
|
VkResult err;
|
|
|
|
// Vulkan instance
|
|
err = createInstance(enableValidation);
|
|
if (err)
|
|
{
|
|
vkTools::exitFatal("Could not create Vulkan instance : \n" + vkTools::errorString(err), "Fatal error");
|
|
}
|
|
|
|
#if defined(__ANDROID__)
|
|
loadVulkanFunctions(instance);
|
|
#endif
|
|
|
|
// If requested, we enable the default validation layers for debugging
|
|
if (enableValidation)
|
|
{
|
|
// The report flags determine what type of messages for the layers will be displayed
|
|
// For validating (debugging) an appplication the error and warning bits should suffice
|
|
VkDebugReportFlagsEXT debugReportFlags = VK_DEBUG_REPORT_ERROR_BIT_EXT; // | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT;
|
|
// Additional flags include performance info, loader and layer debug messages, etc.
|
|
vkDebug::setupDebugging(instance, debugReportFlags, VK_NULL_HANDLE);
|
|
}
|
|
|
|
// Physical device
|
|
uint32_t gpuCount = 0;
|
|
// Get number of available physical devices
|
|
VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &gpuCount, nullptr));
|
|
assert(gpuCount > 0);
|
|
// Enumerate devices
|
|
std::vector<VkPhysicalDevice> physicalDevices(gpuCount);
|
|
err = vkEnumeratePhysicalDevices(instance, &gpuCount, physicalDevices.data());
|
|
if (err)
|
|
{
|
|
vkTools::exitFatal("Could not enumerate physical devices : \n" + vkTools::errorString(err), "Fatal error");
|
|
}
|
|
|
|
// Note :
|
|
// This example will always use the first physical device reported,
|
|
// change the vector index if you have multiple Vulkan devices installed
|
|
// and want to use another one
|
|
physicalDevice = physicalDevices[0];
|
|
|
|
// Vulkan device creation
|
|
// This is handled by a separate class that gets a logical device representation
|
|
// and encapsulates functions related to a device
|
|
vulkanDevice = new vk::VulkanDevice(physicalDevice);
|
|
VK_CHECK_RESULT(vulkanDevice->createLogicalDevice(enabledFeatures));
|
|
device = vulkanDevice->logicalDevice;
|
|
|
|
// todo: remove
|
|
// Store properties (including limits) and features of the phyiscal device
|
|
// So examples can check against them and see if a feature is actually supported
|
|
vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
|
|
vkGetPhysicalDeviceFeatures(physicalDevice, &deviceFeatures);
|
|
// Gather physical device memory properties
|
|
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties);
|
|
|
|
// Get a graphics queue from the device
|
|
vkGetDeviceQueue(device, vulkanDevice->queueFamilyIndices.graphics, 0, &queue);
|
|
|
|
// Find a suitable depth format
|
|
VkBool32 validDepthFormat = vkTools::getSupportedDepthFormat(physicalDevice, &depthFormat);
|
|
assert(validDepthFormat);
|
|
|
|
swapChain.connect(instance, physicalDevice, device);
|
|
|
|
// Create synchronization objects
|
|
VkSemaphoreCreateInfo semaphoreCreateInfo = vkTools::initializers::semaphoreCreateInfo();
|
|
// Create a semaphore used to synchronize image presentation
|
|
// Ensures that the image is displayed before we start submitting new commands to the queu
|
|
VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &semaphores.presentComplete));
|
|
// Create a semaphore used to synchronize command submission
|
|
// Ensures that the image is not presented until all commands have been sumbitted and executed
|
|
VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &semaphores.renderComplete));
|
|
// Create a semaphore used to synchronize command submission
|
|
// Ensures that the image is not presented until all commands for the text overlay have been sumbitted and executed
|
|
// Will be inserted after the render complete semaphore if the text overlay is enabled
|
|
VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &semaphores.textOverlayComplete));
|
|
|
|
// Set up submit info structure
|
|
// Semaphores will stay the same during application lifetime
|
|
// Command buffer submission info is set by each example
|
|
submitInfo = vkTools::initializers::submitInfo();
|
|
submitInfo.pWaitDstStageMask = &submitPipelineStages;
|
|
submitInfo.waitSemaphoreCount = 1;
|
|
submitInfo.pWaitSemaphores = &semaphores.presentComplete;
|
|
submitInfo.signalSemaphoreCount = 1;
|
|
submitInfo.pSignalSemaphores = &semaphores.renderComplete;
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
// Win32 : Sets up a console window and redirects standard output to it
|
|
void VulkanExampleBase::setupConsole(std::string title)
|
|
{
|
|
AllocConsole();
|
|
AttachConsole(GetCurrentProcessId());
|
|
FILE *stream;
|
|
freopen_s(&stream, "CONOUT$", "w+", stdout);
|
|
SetConsoleTitle(TEXT(title.c_str()));
|
|
}
|
|
|
|
HWND VulkanExampleBase::setupWindow(HINSTANCE hinstance, WNDPROC wndproc)
|
|
{
|
|
this->windowInstance = hinstance;
|
|
|
|
bool fullscreen = false;
|
|
for (auto arg : args)
|
|
{
|
|
if (arg == std::string("-fullscreen"))
|
|
{
|
|
fullscreen = true;
|
|
}
|
|
}
|
|
|
|
WNDCLASSEX wndClass;
|
|
|
|
wndClass.cbSize = sizeof(WNDCLASSEX);
|
|
wndClass.style = CS_HREDRAW | CS_VREDRAW;
|
|
wndClass.lpfnWndProc = wndproc;
|
|
wndClass.cbClsExtra = 0;
|
|
wndClass.cbWndExtra = 0;
|
|
wndClass.hInstance = hinstance;
|
|
wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
|
|
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
|
wndClass.lpszMenuName = NULL;
|
|
wndClass.lpszClassName = name.c_str();
|
|
wndClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO);
|
|
|
|
if (!RegisterClassEx(&wndClass))
|
|
{
|
|
std::cout << "Could not register window class!\n";
|
|
fflush(stdout);
|
|
exit(1);
|
|
}
|
|
|
|
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
|
|
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
|
|
|
|
if (fullscreen)
|
|
{
|
|
DEVMODE dmScreenSettings;
|
|
memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));
|
|
dmScreenSettings.dmSize = sizeof(dmScreenSettings);
|
|
dmScreenSettings.dmPelsWidth = screenWidth;
|
|
dmScreenSettings.dmPelsHeight = screenHeight;
|
|
dmScreenSettings.dmBitsPerPel = 32;
|
|
dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
|
|
|
|
if ((width != screenWidth) && (height != screenHeight))
|
|
{
|
|
if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
|
|
{
|
|
if (MessageBox(NULL, "Fullscreen Mode not supported!\n Switch to window mode?", "Error", MB_YESNO | MB_ICONEXCLAMATION) == IDYES)
|
|
{
|
|
fullscreen = FALSE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
DWORD dwExStyle;
|
|
DWORD dwStyle;
|
|
|
|
if (fullscreen)
|
|
{
|
|
dwExStyle = WS_EX_APPWINDOW;
|
|
dwStyle = WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
|
|
}
|
|
else
|
|
{
|
|
dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
|
|
dwStyle = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
|
|
}
|
|
|
|
RECT windowRect;
|
|
windowRect.left = 0L;
|
|
windowRect.top = 0L;
|
|
windowRect.right = fullscreen ? (long)screenWidth : (long)width;
|
|
windowRect.bottom = fullscreen ? (long)screenHeight : (long)height;
|
|
|
|
AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle);
|
|
|
|
std::string windowTitle = getWindowTitle();
|
|
window = CreateWindowEx(0,
|
|
name.c_str(),
|
|
windowTitle.c_str(),
|
|
dwStyle | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
|
|
0,
|
|
0,
|
|
windowRect.right - windowRect.left,
|
|
windowRect.bottom - windowRect.top,
|
|
NULL,
|
|
NULL,
|
|
hinstance,
|
|
NULL);
|
|
|
|
if (!fullscreen)
|
|
{
|
|
// Center on screen
|
|
uint32_t x = (GetSystemMetrics(SM_CXSCREEN) - windowRect.right) / 2;
|
|
uint32_t y = (GetSystemMetrics(SM_CYSCREEN) - windowRect.bottom) / 2;
|
|
SetWindowPos(window, 0, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
|
|
}
|
|
|
|
if (!window)
|
|
{
|
|
printf("Could not create window!\n");
|
|
fflush(stdout);
|
|
return 0;
|
|
exit(1);
|
|
}
|
|
|
|
ShowWindow(window, SW_SHOW);
|
|
SetForegroundWindow(window);
|
|
SetFocus(window);
|
|
|
|
return window;
|
|
}
|
|
|
|
void VulkanExampleBase::handleMessages(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_CLOSE:
|
|
prepared = false;
|
|
DestroyWindow(hWnd);
|
|
PostQuitMessage(0);
|
|
break;
|
|
case WM_PAINT:
|
|
ValidateRect(window, NULL);
|
|
break;
|
|
case WM_KEYDOWN:
|
|
switch (wParam)
|
|
{
|
|
case KEY_P:
|
|
paused = !paused;
|
|
break;
|
|
case KEY_F1:
|
|
if (enableTextOverlay)
|
|
{
|
|
textOverlay->visible = !textOverlay->visible;
|
|
}
|
|
break;
|
|
case KEY_ESCAPE:
|
|
PostQuitMessage(0);
|
|
break;
|
|
}
|
|
|
|
if (camera.firstperson)
|
|
{
|
|
switch (wParam)
|
|
{
|
|
case KEY_W:
|
|
camera.keys.up = true;
|
|
break;
|
|
case KEY_S:
|
|
camera.keys.down = true;
|
|
break;
|
|
case KEY_A:
|
|
camera.keys.left = true;
|
|
break;
|
|
case KEY_D:
|
|
camera.keys.right = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
keyPressed((uint32_t)wParam);
|
|
break;
|
|
case WM_KEYUP:
|
|
if (camera.firstperson)
|
|
{
|
|
switch (wParam)
|
|
{
|
|
case KEY_W:
|
|
camera.keys.up = false;
|
|
break;
|
|
case KEY_S:
|
|
camera.keys.down = false;
|
|
break;
|
|
case KEY_A:
|
|
camera.keys.left = false;
|
|
break;
|
|
case KEY_D:
|
|
camera.keys.right = false;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case WM_RBUTTONDOWN:
|
|
case WM_LBUTTONDOWN:
|
|
case WM_MBUTTONDOWN:
|
|
mousePos.x = (float)LOWORD(lParam);
|
|
mousePos.y = (float)HIWORD(lParam);
|
|
break;
|
|
case WM_MOUSEWHEEL:
|
|
{
|
|
short wheelDelta = GET_WHEEL_DELTA_WPARAM(wParam);
|
|
zoom += (float)wheelDelta * 0.005f * zoomSpeed;
|
|
camera.translate(glm::vec3(0.0f, 0.0f, (float)wheelDelta * 0.005f * zoomSpeed));
|
|
viewUpdated = true;
|
|
break;
|
|
}
|
|
case WM_MOUSEMOVE:
|
|
if (wParam & MK_RBUTTON)
|
|
{
|
|
int32_t posx = LOWORD(lParam);
|
|
int32_t posy = HIWORD(lParam);
|
|
zoom += (mousePos.y - (float)posy) * .005f * zoomSpeed;
|
|
camera.translate(glm::vec3(-0.0f, 0.0f, (mousePos.y - (float)posy) * .005f * zoomSpeed));
|
|
mousePos = glm::vec2((float)posx, (float)posy);
|
|
viewUpdated = true;
|
|
}
|
|
if (wParam & MK_LBUTTON)
|
|
{
|
|
int32_t posx = LOWORD(lParam);
|
|
int32_t posy = HIWORD(lParam);
|
|
rotation.x += (mousePos.y - (float)posy) * 1.25f * rotationSpeed;
|
|
rotation.y -= (mousePos.x - (float)posx) * 1.25f * rotationSpeed;
|
|
camera.rotate(glm::vec3((mousePos.y - (float)posy) * camera.rotationSpeed, -(mousePos.x - (float)posx) * camera.rotationSpeed, 0.0f));
|
|
mousePos = glm::vec2((float)posx, (float)posy);
|
|
viewUpdated = true;
|
|
}
|
|
if (wParam & MK_MBUTTON)
|
|
{
|
|
int32_t posx = LOWORD(lParam);
|
|
int32_t posy = HIWORD(lParam);
|
|
cameraPos.x -= (mousePos.x - (float)posx) * 0.01f;
|
|
cameraPos.y -= (mousePos.y - (float)posy) * 0.01f;
|
|
camera.translate(glm::vec3(-(mousePos.x - (float)posx) * 0.01f, -(mousePos.y - (float)posy) * 0.01f, 0.0f));
|
|
viewUpdated = true;
|
|
mousePos.x = (float)posx;
|
|
mousePos.y = (float)posy;
|
|
}
|
|
break;
|
|
case WM_SIZE:
|
|
if ((prepared) && (wParam != SIZE_MINIMIZED))
|
|
{
|
|
if ((resizing) || ((wParam == SIZE_MAXIMIZED) || (wParam == SIZE_RESTORED)))
|
|
{
|
|
destWidth = LOWORD(lParam);
|
|
destHeight = HIWORD(lParam);
|
|
windowResize();
|
|
}
|
|
}
|
|
break;
|
|
case WM_ENTERSIZEMOVE:
|
|
resizing = true;
|
|
break;
|
|
case WM_EXITSIZEMOVE:
|
|
resizing = false;
|
|
break;
|
|
}
|
|
}
|
|
#elif defined(__ANDROID__)
|
|
int32_t VulkanExampleBase::handleAppInput(struct android_app* app, AInputEvent* event)
|
|
{
|
|
VulkanExampleBase* vulkanExample = reinterpret_cast<VulkanExampleBase*>(app->userData);
|
|
if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION)
|
|
{
|
|
if (AInputEvent_getSource(event) == AINPUT_SOURCE_JOYSTICK)
|
|
{
|
|
// Left thumbstick
|
|
vulkanExample->gamePadState.axisLeft.x = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_X, 0);
|
|
vulkanExample->gamePadState.axisLeft.y = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_Y, 0);
|
|
// Right thumbstick
|
|
vulkanExample->gamePadState.axisRight.x = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_Z, 0);
|
|
vulkanExample->gamePadState.axisRight.y = AMotionEvent_getAxisValue(event, AMOTION_EVENT_AXIS_RZ, 0);
|
|
}
|
|
else
|
|
{
|
|
// todo : touch input
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_KEY)
|
|
{
|
|
int32_t keyCode = AKeyEvent_getKeyCode((const AInputEvent*)event);
|
|
int32_t action = AKeyEvent_getAction((const AInputEvent*)event);
|
|
int32_t button = 0;
|
|
|
|
if (action == AKEY_EVENT_ACTION_UP)
|
|
return 0;
|
|
|
|
switch (keyCode)
|
|
{
|
|
case AKEYCODE_BUTTON_A:
|
|
vulkanExample->keyPressed(GAMEPAD_BUTTON_A);
|
|
break;
|
|
case AKEYCODE_BUTTON_B:
|
|
vulkanExample->keyPressed(GAMEPAD_BUTTON_B);
|
|
break;
|
|
case AKEYCODE_BUTTON_X:
|
|
vulkanExample->keyPressed(GAMEPAD_BUTTON_X);
|
|
break;
|
|
case AKEYCODE_BUTTON_Y:
|
|
vulkanExample->keyPressed(GAMEPAD_BUTTON_Y);
|
|
break;
|
|
case AKEYCODE_BUTTON_L1:
|
|
vulkanExample->keyPressed(GAMEPAD_BUTTON_L1);
|
|
break;
|
|
case AKEYCODE_BUTTON_R1:
|
|
vulkanExample->keyPressed(GAMEPAD_BUTTON_R1);
|
|
break;
|
|
case AKEYCODE_BUTTON_START:
|
|
vulkanExample->paused = !vulkanExample->paused;
|
|
break;
|
|
};
|
|
|
|
LOGD("Button %d pressed", keyCode);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void VulkanExampleBase::handleAppCommand(android_app * app, int32_t cmd)
|
|
{
|
|
assert(app->userData != NULL);
|
|
VulkanExampleBase* vulkanExample = reinterpret_cast<VulkanExampleBase*>(app->userData);
|
|
switch (cmd)
|
|
{
|
|
case APP_CMD_SAVE_STATE:
|
|
LOGD("APP_CMD_SAVE_STATE");
|
|
/*
|
|
vulkanExample->app->savedState = malloc(sizeof(struct saved_state));
|
|
*((struct saved_state*)vulkanExample->app->savedState) = vulkanExample->state;
|
|
vulkanExample->app->savedStateSize = sizeof(struct saved_state);
|
|
*/
|
|
break;
|
|
case APP_CMD_INIT_WINDOW:
|
|
LOGD("APP_CMD_INIT_WINDOW");
|
|
if (vulkanExample->androidApp->window != NULL)
|
|
{
|
|
vulkanExample->initVulkan();
|
|
vulkanExample->initSwapchain();
|
|
vulkanExample->prepare();
|
|
assert(vulkanExample->prepared);
|
|
}
|
|
else
|
|
{
|
|
LOGE("No window assigned!");
|
|
}
|
|
break;
|
|
case APP_CMD_LOST_FOCUS:
|
|
LOGD("APP_CMD_LOST_FOCUS");
|
|
vulkanExample->focused = false;
|
|
break;
|
|
case APP_CMD_GAINED_FOCUS:
|
|
LOGD("APP_CMD_GAINED_FOCUS");
|
|
vulkanExample->focused = true;
|
|
break;
|
|
case APP_CMD_TERM_WINDOW:
|
|
// Window is hidden or closed, clean up resources
|
|
LOGD("APP_CMD_TERM_WINDOW");
|
|
vulkanExample->swapChain.cleanup();
|
|
break;
|
|
}
|
|
}
|
|
#elif defined(_DIRECT2DISPLAY)
|
|
#elif defined(__linux__)
|
|
// Set up a window using XCB and request event types
|
|
xcb_window_t VulkanExampleBase::setupWindow()
|
|
{
|
|
uint32_t value_mask, value_list[32];
|
|
|
|
window = xcb_generate_id(connection);
|
|
|
|
value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
|
|
value_list[0] = screen->black_pixel;
|
|
value_list[1] =
|
|
XCB_EVENT_MASK_KEY_RELEASE |
|
|
XCB_EVENT_MASK_KEY_PRESS |
|
|
XCB_EVENT_MASK_EXPOSURE |
|
|
XCB_EVENT_MASK_STRUCTURE_NOTIFY |
|
|
XCB_EVENT_MASK_POINTER_MOTION |
|
|
XCB_EVENT_MASK_BUTTON_PRESS |
|
|
XCB_EVENT_MASK_BUTTON_RELEASE;
|
|
|
|
xcb_create_window(connection,
|
|
XCB_COPY_FROM_PARENT,
|
|
window, screen->root,
|
|
0, 0, width, height, 0,
|
|
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
|
screen->root_visual,
|
|
value_mask, value_list);
|
|
|
|
/* Magic code that will send notification when window is destroyed */
|
|
xcb_intern_atom_cookie_t cookie = xcb_intern_atom(connection, 1, 12, "WM_PROTOCOLS");
|
|
xcb_intern_atom_reply_t* reply = xcb_intern_atom_reply(connection, cookie, 0);
|
|
|
|
xcb_intern_atom_cookie_t cookie2 = xcb_intern_atom(connection, 0, 16, "WM_DELETE_WINDOW");
|
|
atom_wm_delete_window = xcb_intern_atom_reply(connection, cookie2, 0);
|
|
|
|
xcb_change_property(connection, XCB_PROP_MODE_REPLACE,
|
|
window, (*reply).atom, 4, 32, 1,
|
|
&(*atom_wm_delete_window).atom);
|
|
|
|
std::string windowTitle = getWindowTitle();
|
|
xcb_change_property(connection, XCB_PROP_MODE_REPLACE,
|
|
window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
|
|
title.size(), windowTitle.c_str());
|
|
|
|
free(reply);
|
|
|
|
xcb_map_window(connection, window);
|
|
|
|
return(window);
|
|
}
|
|
|
|
// Initialize XCB connection
|
|
void VulkanExampleBase::initxcbConnection()
|
|
{
|
|
const xcb_setup_t *setup;
|
|
xcb_screen_iterator_t iter;
|
|
int scr;
|
|
|
|
connection = xcb_connect(NULL, &scr);
|
|
if (connection == NULL) {
|
|
printf("Could not find a compatible Vulkan ICD!\n");
|
|
fflush(stdout);
|
|
exit(1);
|
|
}
|
|
|
|
setup = xcb_get_setup(connection);
|
|
iter = xcb_setup_roots_iterator(setup);
|
|
while (scr-- > 0)
|
|
xcb_screen_next(&iter);
|
|
screen = iter.data;
|
|
}
|
|
|
|
void VulkanExampleBase::handleEvent(const xcb_generic_event_t *event)
|
|
{
|
|
switch (event->response_type & 0x7f)
|
|
{
|
|
case XCB_CLIENT_MESSAGE:
|
|
if ((*(xcb_client_message_event_t*)event).data.data32[0] ==
|
|
(*atom_wm_delete_window).atom) {
|
|
quit = true;
|
|
}
|
|
break;
|
|
case XCB_MOTION_NOTIFY:
|
|
{
|
|
xcb_motion_notify_event_t *motion = (xcb_motion_notify_event_t *)event;
|
|
if (mouseButtons.left)
|
|
{
|
|
rotation.x += (mousePos.y - (float)motion->event_y) * 1.25f;
|
|
rotation.y -= (mousePos.x - (float)motion->event_x) * 1.25f;
|
|
camera.rotate(glm::vec3((mousePos.y - (float)motion->event_y) * camera.rotationSpeed, -(mousePos.x - (float)motion->event_x) * camera.rotationSpeed, 0.0f));
|
|
viewUpdated = true;
|
|
}
|
|
if (mouseButtons.right)
|
|
{
|
|
zoom += (mousePos.y - (float)motion->event_y) * .005f;
|
|
camera.translate(glm::vec3(-0.0f, 0.0f, (mousePos.y - (float)motion->event_y) * .005f * zoomSpeed));
|
|
viewUpdated = true;
|
|
}
|
|
if (mouseButtons.middle)
|
|
{
|
|
cameraPos.x -= (mousePos.x - (float)motion->event_x) * 0.01f;
|
|
cameraPos.y -= (mousePos.y - (float)motion->event_y) * 0.01f;
|
|
camera.translate(glm::vec3(-(mousePos.x - (float)(float)motion->event_x) * 0.01f, -(mousePos.y - (float)motion->event_y) * 0.01f, 0.0f));
|
|
viewUpdated = true;
|
|
mousePos.x = (float)motion->event_x;
|
|
mousePos.y = (float)motion->event_y;
|
|
}
|
|
mousePos = glm::vec2((float)motion->event_x, (float)motion->event_y);
|
|
}
|
|
break;
|
|
case XCB_BUTTON_PRESS:
|
|
{
|
|
xcb_button_press_event_t *press = (xcb_button_press_event_t *)event;
|
|
if (press->detail == XCB_BUTTON_INDEX_1)
|
|
mouseButtons.left = true;
|
|
if (press->detail == XCB_BUTTON_INDEX_2)
|
|
mouseButtons.middle = true;
|
|
if (press->detail == XCB_BUTTON_INDEX_3)
|
|
mouseButtons.right = true;
|
|
}
|
|
break;
|
|
case XCB_BUTTON_RELEASE:
|
|
{
|
|
xcb_button_press_event_t *press = (xcb_button_press_event_t *)event;
|
|
if (press->detail == XCB_BUTTON_INDEX_1)
|
|
mouseButtons.left = false;
|
|
if (press->detail == XCB_BUTTON_INDEX_2)
|
|
mouseButtons.middle = false;
|
|
if (press->detail == XCB_BUTTON_INDEX_3)
|
|
mouseButtons.right = false;
|
|
}
|
|
break;
|
|
case XCB_KEY_PRESS:
|
|
{
|
|
const xcb_key_release_event_t *keyEvent = (const xcb_key_release_event_t *)event;
|
|
switch (keyEvent->detail)
|
|
{
|
|
case KEY_W:
|
|
camera.keys.up = true;
|
|
break;
|
|
case KEY_S:
|
|
camera.keys.down = true;
|
|
break;
|
|
case KEY_A:
|
|
camera.keys.left = true;
|
|
break;
|
|
case KEY_D:
|
|
camera.keys.right = true;
|
|
break;
|
|
case KEY_P:
|
|
paused = !paused;
|
|
break;
|
|
case KEY_F1:
|
|
if (enableTextOverlay)
|
|
{
|
|
textOverlay->visible = !textOverlay->visible;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case XCB_KEY_RELEASE:
|
|
{
|
|
const xcb_key_release_event_t *keyEvent = (const xcb_key_release_event_t *)event;
|
|
switch (keyEvent->detail)
|
|
{
|
|
case KEY_W:
|
|
camera.keys.up = false;
|
|
break;
|
|
case KEY_S:
|
|
camera.keys.down = false;
|
|
break;
|
|
case KEY_A:
|
|
camera.keys.left = false;
|
|
break;
|
|
case KEY_D:
|
|
camera.keys.right = false;
|
|
break;
|
|
case KEY_ESCAPE:
|
|
quit = true;
|
|
break;
|
|
}
|
|
keyPressed(keyEvent->detail);
|
|
}
|
|
break;
|
|
case XCB_DESTROY_NOTIFY:
|
|
quit = true;
|
|
break;
|
|
case XCB_CONFIGURE_NOTIFY:
|
|
{
|
|
const xcb_configure_notify_event_t *cfgEvent = (const xcb_configure_notify_event_t *)event;
|
|
if ((prepared) && ((cfgEvent->width != width) || (cfgEvent->height != height)))
|
|
{
|
|
destWidth = cfgEvent->width;
|
|
destHeight = cfgEvent->height;
|
|
if ((destWidth > 0) && (destHeight > 0))
|
|
{
|
|
windowResize();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void VulkanExampleBase::viewChanged()
|
|
{
|
|
// Can be overrdiden in derived class
|
|
}
|
|
|
|
void VulkanExampleBase::keyPressed(uint32_t keyCode)
|
|
{
|
|
// Can be overriden in derived class
|
|
}
|
|
|
|
void VulkanExampleBase::buildCommandBuffers()
|
|
{
|
|
// Can be overriden in derived class
|
|
}
|
|
|
|
void VulkanExampleBase::createCommandPool()
|
|
{
|
|
VkCommandPoolCreateInfo cmdPoolInfo = {};
|
|
cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
|
cmdPoolInfo.queueFamilyIndex = swapChain.queueNodeIndex;
|
|
cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
|
VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &cmdPool));
|
|
}
|
|
|
|
void VulkanExampleBase::setupDepthStencil()
|
|
{
|
|
VkImageCreateInfo image = {};
|
|
image.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
image.pNext = NULL;
|
|
image.imageType = VK_IMAGE_TYPE_2D;
|
|
image.format = depthFormat;
|
|
image.extent = { width, height, 1 };
|
|
image.mipLevels = 1;
|
|
image.arrayLayers = 1;
|
|
image.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
image.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
image.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
|
image.flags = 0;
|
|
|
|
VkMemoryAllocateInfo mem_alloc = {};
|
|
mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
mem_alloc.pNext = NULL;
|
|
mem_alloc.allocationSize = 0;
|
|
mem_alloc.memoryTypeIndex = 0;
|
|
|
|
VkImageViewCreateInfo depthStencilView = {};
|
|
depthStencilView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
depthStencilView.pNext = NULL;
|
|
depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
depthStencilView.format = depthFormat;
|
|
depthStencilView.flags = 0;
|
|
depthStencilView.subresourceRange = {};
|
|
depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
depthStencilView.subresourceRange.baseMipLevel = 0;
|
|
depthStencilView.subresourceRange.levelCount = 1;
|
|
depthStencilView.subresourceRange.baseArrayLayer = 0;
|
|
depthStencilView.subresourceRange.layerCount = 1;
|
|
|
|
VkMemoryRequirements memReqs;
|
|
|
|
VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &depthStencil.image));
|
|
vkGetImageMemoryRequirements(device, depthStencil.image, &memReqs);
|
|
mem_alloc.allocationSize = memReqs.size;
|
|
mem_alloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
|
VK_CHECK_RESULT(vkAllocateMemory(device, &mem_alloc, nullptr, &depthStencil.mem));
|
|
VK_CHECK_RESULT(vkBindImageMemory(device, depthStencil.image, depthStencil.mem, 0));
|
|
|
|
depthStencilView.image = depthStencil.image;
|
|
VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilView, nullptr, &depthStencil.view));
|
|
}
|
|
|
|
void VulkanExampleBase::setupFrameBuffer()
|
|
{
|
|
VkImageView attachments[2];
|
|
|
|
// Depth/Stencil attachment is the same for all frame buffers
|
|
attachments[1] = depthStencil.view;
|
|
|
|
VkFramebufferCreateInfo frameBufferCreateInfo = {};
|
|
frameBufferCreateInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
|
frameBufferCreateInfo.pNext = NULL;
|
|
frameBufferCreateInfo.renderPass = renderPass;
|
|
frameBufferCreateInfo.attachmentCount = 2;
|
|
frameBufferCreateInfo.pAttachments = attachments;
|
|
frameBufferCreateInfo.width = width;
|
|
frameBufferCreateInfo.height = height;
|
|
frameBufferCreateInfo.layers = 1;
|
|
|
|
// Create frame buffers for every swap chain image
|
|
frameBuffers.resize(swapChain.imageCount);
|
|
for (uint32_t i = 0; i < frameBuffers.size(); i++)
|
|
{
|
|
attachments[0] = swapChain.buffers[i].view;
|
|
VK_CHECK_RESULT(vkCreateFramebuffer(device, &frameBufferCreateInfo, nullptr, &frameBuffers[i]));
|
|
}
|
|
}
|
|
|
|
void VulkanExampleBase::setupRenderPass()
|
|
{
|
|
std::array<VkAttachmentDescription, 2> attachments = {};
|
|
// Color attachment
|
|
attachments[0].format = colorformat;
|
|
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
|
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
|
// Depth attachment
|
|
attachments[1].format = depthFormat;
|
|
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
|
|
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
|
attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
|
|
VkAttachmentReference colorReference = {};
|
|
colorReference.attachment = 0;
|
|
colorReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
|
|
|
VkAttachmentReference depthReference = {};
|
|
depthReference.attachment = 1;
|
|
depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
|
|
VkSubpassDescription subpassDescription = {};
|
|
subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
subpassDescription.colorAttachmentCount = 1;
|
|
subpassDescription.pColorAttachments = &colorReference;
|
|
subpassDescription.pDepthStencilAttachment = &depthReference;
|
|
subpassDescription.inputAttachmentCount = 0;
|
|
subpassDescription.pInputAttachments = nullptr;
|
|
subpassDescription.preserveAttachmentCount = 0;
|
|
subpassDescription.pPreserveAttachments = nullptr;
|
|
subpassDescription.pResolveAttachments = nullptr;
|
|
|
|
// Subpass dependencies for layout transitions
|
|
std::array<VkSubpassDependency, 2> dependencies;
|
|
|
|
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
|
dependencies[0].dstSubpass = 0;
|
|
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
|
dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
|
dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
|
|
|
dependencies[1].srcSubpass = 0;
|
|
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
|
dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
|
dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
|
dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
|
|
|
VkRenderPassCreateInfo renderPassInfo = {};
|
|
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
|
renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
|
|
renderPassInfo.pAttachments = attachments.data();
|
|
renderPassInfo.subpassCount = 1;
|
|
renderPassInfo.pSubpasses = &subpassDescription;
|
|
renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
|
|
renderPassInfo.pDependencies = dependencies.data();
|
|
|
|
VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass));
|
|
}
|
|
|
|
void VulkanExampleBase::windowResize()
|
|
{
|
|
if (!prepared)
|
|
{
|
|
return;
|
|
}
|
|
prepared = false;
|
|
|
|
// Recreate swap chain
|
|
width = destWidth;
|
|
height = destHeight;
|
|
setupSwapChain();
|
|
|
|
// Recreate the frame buffers
|
|
|
|
vkDestroyImageView(device, depthStencil.view, nullptr);
|
|
vkDestroyImage(device, depthStencil.image, nullptr);
|
|
vkFreeMemory(device, depthStencil.mem, nullptr);
|
|
setupDepthStencil();
|
|
|
|
for (uint32_t i = 0; i < frameBuffers.size(); i++)
|
|
{
|
|
vkDestroyFramebuffer(device, frameBuffers[i], nullptr);
|
|
}
|
|
setupFrameBuffer();
|
|
|
|
// Command buffers need to be recreated as they may store
|
|
// references to the recreated frame buffer
|
|
destroyCommandBuffers();
|
|
createCommandBuffers();
|
|
buildCommandBuffers();
|
|
|
|
vkQueueWaitIdle(queue);
|
|
vkDeviceWaitIdle(device);
|
|
|
|
if (enableTextOverlay)
|
|
{
|
|
textOverlay->reallocateCommandBuffers();
|
|
updateTextOverlay();
|
|
}
|
|
|
|
camera.updateAspectRatio((float)width / (float)height);
|
|
|
|
// Notify derived class
|
|
windowResized();
|
|
viewChanged();
|
|
|
|
prepared = true;
|
|
}
|
|
|
|
void VulkanExampleBase::windowResized()
|
|
{
|
|
// Can be overriden in derived class
|
|
}
|
|
|
|
void VulkanExampleBase::initSwapchain()
|
|
{
|
|
#if defined(_WIN32)
|
|
swapChain.initSurface(windowInstance, window);
|
|
#elif defined(__ANDROID__)
|
|
swapChain.initSurface(androidApp->window);
|
|
#elif defined(_DIRECT2DISPLAY)
|
|
swapChain.initSurface(width, height);
|
|
#elif defined(__linux__)
|
|
swapChain.initSurface(connection, window);
|
|
#endif
|
|
}
|
|
|
|
void VulkanExampleBase::setupSwapChain()
|
|
{
|
|
swapChain.create(&width, &height, enableVSync);
|
|
}
|