From a9fe22426ff2746e870e595c67c31ec2484d7fda Mon Sep 17 00:00:00 2001 From: saschawillems Date: Wed, 23 Mar 2016 22:17:14 +0100 Subject: [PATCH] Android support for Compute particles example, replacing separate Android version (#97) --- android/base/vulkanandroidbase.hpp | 881 ------------------ android/build-computeparticles.bat | 1 + android/computeparticles/.gitignore | 10 + android/computeparticles/AndroidManifest.xml | 27 + android/computeparticles/build.bat | 20 + .../android_native_app_glue.c | 426 --------- .../android_native_app_glue.h | 344 ------- .../computeparticles.NativeActivity.vcxproj | 224 ----- ...teparticles.NativeActivity.vcxproj.filters | 15 - .../computeparticles.cpp | 822 ---------------- .../computeparticles.NativeActivity/pch.h | 22 - .../AndroidManifest.xml | 34 - .../assets/shaders/particle.comp.spv | Bin 3664 -> 0 bytes .../assets/shaders/particle.frag.spv | Bin 748 -> 0 bytes .../assets/shaders/particle.vert.spv | Bin 1144 -> 0 bytes .../assets/textures/android_robot.ktx | Bin 349656 -> 0 bytes .../computeparticles.Packaging/build.xml | 90 -- .../computeparticles.Packaging.androidproj | 135 --- .../project.properties | 3 - .../res/drawable/banner.png | Bin 26204 -> 0 bytes .../res/drawable/icon.png | Bin 33788 -> 0 bytes .../res/values/strings.xml | 4 - android/computeparticles/jni/Android.mk | 47 + android/computeparticles/jni/Application.mk | 5 + android/vulkanAndroid.sln | 216 ----- computeparticles/computeparticles.cpp | 46 +- 26 files changed, 142 insertions(+), 3230 deletions(-) delete mode 100644 android/base/vulkanandroidbase.hpp create mode 100644 android/build-computeparticles.bat create mode 100644 android/computeparticles/.gitignore create mode 100644 android/computeparticles/AndroidManifest.xml create mode 100644 android/computeparticles/build.bat delete mode 100644 android/computeparticles/computeparticles.NativeActivity/android_native_app_glue.c delete mode 100644 android/computeparticles/computeparticles.NativeActivity/android_native_app_glue.h delete mode 100644 android/computeparticles/computeparticles.NativeActivity/computeparticles.NativeActivity.vcxproj delete mode 100644 android/computeparticles/computeparticles.NativeActivity/computeparticles.NativeActivity.vcxproj.filters delete mode 100644 android/computeparticles/computeparticles.NativeActivity/computeparticles.cpp delete mode 100644 android/computeparticles/computeparticles.NativeActivity/pch.h delete mode 100644 android/computeparticles/computeparticles.Packaging/AndroidManifest.xml delete mode 100644 android/computeparticles/computeparticles.Packaging/assets/shaders/particle.comp.spv delete mode 100644 android/computeparticles/computeparticles.Packaging/assets/shaders/particle.frag.spv delete mode 100644 android/computeparticles/computeparticles.Packaging/assets/shaders/particle.vert.spv delete mode 100644 android/computeparticles/computeparticles.Packaging/assets/textures/android_robot.ktx delete mode 100644 android/computeparticles/computeparticles.Packaging/build.xml delete mode 100644 android/computeparticles/computeparticles.Packaging/computeparticles.Packaging.androidproj delete mode 100644 android/computeparticles/computeparticles.Packaging/project.properties delete mode 100644 android/computeparticles/computeparticles.Packaging/res/drawable/banner.png delete mode 100644 android/computeparticles/computeparticles.Packaging/res/drawable/icon.png delete mode 100644 android/computeparticles/computeparticles.Packaging/res/values/strings.xml create mode 100644 android/computeparticles/jni/Android.mk create mode 100644 android/computeparticles/jni/Application.mk delete mode 100644 android/vulkanAndroid.sln diff --git a/android/base/vulkanandroidbase.hpp b/android/base/vulkanandroidbase.hpp deleted file mode 100644 index 596fadd6..00000000 --- a/android/base/vulkanandroidbase.hpp +++ /dev/null @@ -1,881 +0,0 @@ -/* -* Shared android example base -* -* Will be replaced once the main examples get proper android support -* -* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include -#include "vulkanandroid.h" -#include "vulkanswapchain.hpp" -#include "vulkantools.h" - -class VulkanAndroidExampleBase -{ -private: - std::chrono::high_resolution_clock::time_point tStart; - VkShaderModule loadShaderModule(const char *fileName, VkShaderStageFlagBits stage) - { - // Load shader from compressed asset - AAsset* asset = AAssetManager_open(app->activity->assetManager, fileName, AASSET_MODE_STREAMING); - assert(asset); - size_t size = AAsset_getLength(asset); - assert(size > 0); - - char *shaderCode = new char[size]; - AAsset_read(asset, shaderCode, size); - AAsset_close(asset); - - VkShaderModule shaderModule; - VkShaderModuleCreateInfo moduleCreateInfo; - VkResult err; - - moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - moduleCreateInfo.pNext = NULL; - - moduleCreateInfo.codeSize = size; - moduleCreateInfo.pCode = (uint32_t*)shaderCode; - moduleCreateInfo.flags = 0; - err = vkCreateShaderModule(device, &moduleCreateInfo, NULL, &shaderModule); - assert(!err); - - return shaderModule; - } -public: - bool prepared = false; - - struct android_app* app; - uint32_t width; - uint32_t height; - float frameTimer = 1.0f; - - struct Texture { - VkSampler sampler; - VkImage image; - VkImageLayout imageLayout; - VkDeviceMemory deviceMemory; - VkImageView view; - uint32_t width, height; - uint32_t mipLevels; - }; - - VkPhysicalDeviceMemoryProperties deviceMemoryProperties; - VkInstance instance; - VkPhysicalDevice physicalDevice; - VkDevice device; - VkQueue queue; - VkCommandPool cmdPool; - VkCommandBuffer setupCmdBuffer = VK_NULL_HANDLE; - VkCommandBuffer postPresentCmdBuffer = VK_NULL_HANDLE; - VkCommandBuffer prePresentCmdBuffer = VK_NULL_HANDLE; - std::vector drawCmdBuffers; - VkPipelineCache pipelineCache; - VkDescriptorPool descriptorPool; - VulkanSwapChain swapChain; - std::vector shaderModules; - - uint32_t currentBuffer = 0; - struct - { - VkImage image; - VkDeviceMemory mem; - VkImageView view; - } depthStencil; - std::vectorframeBuffers; - VkRenderPass renderPass; - - struct { - VkSemaphore presentComplete; - VkSemaphore submitSignal; - } semaphores; - - VkBool32 getMemoryType(uint32_t typeBits, VkFlags properties, uint32_t * typeIndex) - { - for (uint32_t i = 0; i < 32; i++) - { - if ((typeBits & 1) == 1) - { - if ((deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties) - { - *typeIndex = i; - return true; - } - } - typeBits >>= 1; - } - return false; - } - - void initVulkan() - { - bool libLoaded = loadVulkanLibrary(); - assert(libLoaded); - - VkResult vkRes; - - // Instance - VkApplicationInfo appInfo = {}; - appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - appInfo.pApplicationName = "Vulkan Android Example"; - appInfo.applicationVersion = 1; - appInfo.pEngineName = "VulkanAndroidExample"; - appInfo.engineVersion = 1; - // todo : Workaround to support implementations that are not using the latest SDK - appInfo.apiVersion = VK_MAKE_VERSION(1, 0, 1); - - VkInstanceCreateInfo instanceCreateInfo = {}; - instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - instanceCreateInfo.pApplicationInfo = &appInfo; - - vkRes = vkCreateInstance(&instanceCreateInfo, NULL, &instance); - assert(vkRes == VK_SUCCESS); - - loadVulkanFunctions(instance); - - // Device - // Always use first physical device - uint32_t gpuCount; - vkRes = vkEnumeratePhysicalDevices(instance, &gpuCount, &physicalDevice); - assert(vkRes == VK_SUCCESS); - - // Find a queue that supports graphics operations - uint32_t graphicsQueueIndex = 0; - uint32_t queueCount; - vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, NULL); - assert(queueCount >= 1); - - std::vector queueProps; - queueProps.resize(queueCount); - vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, queueProps.data()); - - for (graphicsQueueIndex = 0; graphicsQueueIndex < queueCount; graphicsQueueIndex++) - { - if (queueProps[graphicsQueueIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT) - break; - } - assert(graphicsQueueIndex < queueCount); - - // Request the queue - float queuePriorities = 0.0f; - VkDeviceQueueCreateInfo queueCreateInfo = {}; - queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queueCreateInfo.queueFamilyIndex = graphicsQueueIndex; - queueCreateInfo.queueCount = 1; - queueCreateInfo.pQueuePriorities = &queuePriorities; - - // Create device - VkDeviceCreateInfo deviceCreateInfo = {}; - deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - deviceCreateInfo.queueCreateInfoCount = 1; - deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo; - - vkRes = vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device); - assert(vkRes == VK_SUCCESS); - - // Get graphics queue - vkGetDeviceQueue(device, graphicsQueueIndex, 0, &queue); - - // Device memory properties (for finding appropriate memory types) - vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties); - - // Swap chain - swapChain.connect(instance, physicalDevice, device); - swapChain.initSurface(app->window); - - // Command buffer pool - 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; - vkRes = vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &cmdPool); - assert(!vkRes); - - // Pipeline cache - VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {}; - pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; - VkResult err = vkCreatePipelineCache(device, &pipelineCacheCreateInfo, nullptr, &pipelineCache); - assert(!err); - - // Create semaphores for synchronization - VkSemaphoreCreateInfo semaphoreCreateInfo = vkTools::initializers::semaphoreCreateInfo(); - - err = vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &semaphores.presentComplete); - assert(!err); - err = vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &semaphores.submitSignal); - assert(!err); - - createSetupCommandBuffer(); - startSetupCommandBuffer(); - - swapChain.create(setupCmdBuffer, &width, &height); - - setupDepthStencil(); - setupRenderPass(); - setupFrameBuffer(); - - flushSetupCommandBuffer(); - - createCommandBuffers(); - } - - void cleanUpVulkan() - { - swapChain.cleanup(); - - vkDestroyDescriptorPool(device, descriptorPool, nullptr); - if (setupCmdBuffer != VK_NULL_HANDLE) - { - vkFreeCommandBuffers(device, cmdPool, 1, &setupCmdBuffer); - - } - vkFreeCommandBuffers(device, cmdPool, drawCmdBuffers.size(), drawCmdBuffers.data()); - vkFreeCommandBuffers(device, cmdPool, 1, &prePresentCmdBuffer); - vkFreeCommandBuffers(device, cmdPool, 1, &postPresentCmdBuffer); - - 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); - - vkDestroySemaphore(device, semaphores.presentComplete, nullptr); - vkDestroySemaphore(device, semaphores.submitSignal, nullptr); - - vkDestroyPipelineCache(device, pipelineCache, nullptr); - vkDestroyDevice(device, nullptr); - vkDestroyInstance(instance, nullptr); - - freeVulkanLibrary(); - } - - VkPipelineShaderStageCreateInfo loadShader(const char * fileName, VkShaderStageFlagBits stage) - { - VkPipelineShaderStageCreateInfo shaderStage = {}; - shaderStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - shaderStage.stage = stage; - shaderStage.module = loadShaderModule(fileName, stage); - shaderStage.pName = "main"; - assert(shaderStage.module != NULL); - shaderModules.push_back(shaderStage.module); - return shaderStage; - } - - void createSetupCommandBuffer() - { - VkCommandBufferAllocateInfo cmdBufAllocateInfo = - vkTools::initializers::commandBufferAllocateInfo( - cmdPool, - VK_COMMAND_BUFFER_LEVEL_PRIMARY, - 1); - - VkResult vkRes = vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &setupCmdBuffer); - assert(!vkRes); - } - - void startSetupCommandBuffer() - { - VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo(); - vkBeginCommandBuffer(setupCmdBuffer, &cmdBufInfo); - } - - void flushSetupCommandBuffer() - { - VkResult err; - - if (setupCmdBuffer == VK_NULL_HANDLE) - return; - - err = vkEndCommandBuffer(setupCmdBuffer); - assert(!err); - - VkSubmitInfo submitInfo = {}; - submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &setupCmdBuffer; - - err = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); - assert(!err); - - err = vkQueueWaitIdle(queue); - assert(!err); - } - - void setupDepthStencil() - { - VkImageCreateInfo image = {}; - image.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - image.pNext = NULL; - image.imageType = VK_IMAGE_TYPE_2D; - image.format = VK_FORMAT_D24_UNORM_S8_UINT; - 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 = VK_FORMAT_D24_UNORM_S8_UINT; - 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; - VkResult err; - - err = vkCreateImage(device, &image, nullptr, &depthStencil.image); - assert(!err); - vkGetImageMemoryRequirements(device, depthStencil.image, &memReqs); - mem_alloc.allocationSize = memReqs.size; - getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &mem_alloc.memoryTypeIndex); - err = vkAllocateMemory(device, &mem_alloc, nullptr, &depthStencil.mem); - assert(!err); - - err = vkBindImageMemory(device, depthStencil.image, depthStencil.mem, 0); - assert(!err); - vkTools::setImageLayout(setupCmdBuffer, depthStencil.image, VK_IMAGE_ASPECT_DEPTH_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - - depthStencilView.image = depthStencil.image; - err = vkCreateImageView(device, &depthStencilView, nullptr, &depthStencil.view); - assert(!err); - } - - void 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; - VkResult err = vkCreateFramebuffer(device, &frameBufferCreateInfo, nullptr, &frameBuffers[i]); - assert(!err); - } - } - - void setupRenderPass() - { - VkAttachmentDescription attachments[2]; - attachments[0].format = VK_FORMAT_R8G8B8A8_UNORM; - 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_COLOR_ATTACHMENT_OPTIMAL; - attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - - attachments[1].format = VK_FORMAT_D24_UNORM_S8_UINT; - 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_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; - 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 subpass = {}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.flags = 0; - subpass.inputAttachmentCount = 0; - subpass.pInputAttachments = NULL; - subpass.colorAttachmentCount = 1; - subpass.pColorAttachments = &colorReference; - subpass.pResolveAttachments = NULL; - subpass.pDepthStencilAttachment = &depthReference; - subpass.preserveAttachmentCount = 0; - subpass.pPreserveAttachments = NULL; - - VkRenderPassCreateInfo renderPassInfo = {}; - renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; - renderPassInfo.pNext = NULL; - renderPassInfo.attachmentCount = 2; - renderPassInfo.pAttachments = attachments; - renderPassInfo.subpassCount = 1; - renderPassInfo.pSubpasses = &subpass; - renderPassInfo.dependencyCount = 0; - renderPassInfo.pDependencies = NULL; - - VkResult err; - - err = vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass); - assert(!err); - } - - void createCommandBuffers() - { - drawCmdBuffers.resize(swapChain.imageCount); - - VkCommandBufferAllocateInfo cmdBufAllocateInfo = - vkTools::initializers::commandBufferAllocateInfo( - cmdPool, - VK_COMMAND_BUFFER_LEVEL_PRIMARY, - drawCmdBuffers.size()); - - VkResult vkRes = vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, drawCmdBuffers.data()); - assert(!vkRes); - - cmdBufAllocateInfo.commandBufferCount = 1; - // Pre present - vkRes = vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &prePresentCmdBuffer); - assert(!vkRes); - // Post present - vkRes = vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &postPresentCmdBuffer); - assert(!vkRes); - } - - void submitPrePresentBarrier(VkImage image) - { - VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo(); - - VkResult vkRes = vkBeginCommandBuffer(prePresentCmdBuffer, &cmdBufInfo); - assert(!vkRes); - - VkImageMemoryBarrier prePresentBarrier = vkTools::initializers::imageMemoryBarrier(); - prePresentBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - prePresentBarrier.dstAccessMask = 0; - prePresentBarrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - prePresentBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - prePresentBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - prePresentBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - prePresentBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; - prePresentBarrier.image = image; - - vkCmdPipelineBarrier( - prePresentCmdBuffer, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_FLAGS_NONE, - 0, nullptr, // No memory barriers, - 0, nullptr, // No buffer barriers, - 1, &prePresentBarrier); - - vkRes = vkEndCommandBuffer(prePresentCmdBuffer); - assert(!vkRes); - - VkSubmitInfo submitInfo = vkTools::initializers::submitInfo(); - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &prePresentCmdBuffer; - - vkRes = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); - assert(!vkRes); - } - - void submitPostPresentBarrier(VkImage image) - { - VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo(); - - VkResult vkRes = vkBeginCommandBuffer(postPresentCmdBuffer, &cmdBufInfo); - assert(!vkRes); - - VkImageMemoryBarrier postPresentBarrier = vkTools::initializers::imageMemoryBarrier(); - postPresentBarrier.srcAccessMask = 0; - postPresentBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - postPresentBarrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; - postPresentBarrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; - postPresentBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - postPresentBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - postPresentBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; - postPresentBarrier.image = image; - - vkCmdPipelineBarrier( - postPresentCmdBuffer, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - 0, - 0, nullptr, // No memory barriers, - 0, nullptr, // No buffer barriers, - 1, &postPresentBarrier); - - vkRes = vkEndCommandBuffer(postPresentCmdBuffer); - assert(!vkRes); - - VkSubmitInfo submitInfo = vkTools::initializers::submitInfo(); - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &postPresentCmdBuffer; - - vkRes = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); - assert(!vkRes); - } - - void loadTexture(const char* fileName, VkFormat format, Texture *texture, bool forceLinearTiling) - { - VkFormatProperties formatProperties; - VkResult err; - - AAsset* asset = AAssetManager_open(app->activity->assetManager, fileName, AASSET_MODE_STREAMING); - assert(asset); - size_t size = AAsset_getLength(asset); - assert(size > 0); - - void *textureData = malloc(size); - AAsset_read(asset, textureData, size); - AAsset_close(asset); - - gli::texture2D tex2D(gli::load((const char*)textureData, size)); - assert(!tex2D.empty()); - - texture->width = tex2D[0].dimensions().x; - texture->height = tex2D[0].dimensions().y; - texture->mipLevels = tex2D.levels(); - - // Get device properites for the requested texture format - vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties); - - // Only use linear tiling if requested (and supported by the device) - // Support for linear tiling is mostly limited, so prefer to use - // optimal tiling instead - // On most implementations linear tiling will only support a very - // limited amount of formats and features (mip maps, cubemaps, arrays, etc.) - VkBool32 useStaging = true; - - // Only use linear tiling if forced - if (forceLinearTiling) - { - // Don't use linear if format is not supported for (linear) shader sampling - useStaging = !(formatProperties.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT); - } - - VkImageCreateInfo imageCreateInfo = vkTools::initializers::imageCreateInfo(); - imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; - imageCreateInfo.format = format; - imageCreateInfo.mipLevels = 1; - imageCreateInfo.arrayLayers = 1; - imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageCreateInfo.tiling = VK_IMAGE_TILING_LINEAR; - imageCreateInfo.usage = (useStaging) ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : VK_IMAGE_USAGE_SAMPLED_BIT; - imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - imageCreateInfo.flags = 0; - imageCreateInfo.extent = { texture->width, texture->height, 1 }; - - VkMemoryAllocateInfo memAllocInfo = vkTools::initializers::memoryAllocateInfo(); - VkMemoryRequirements memReqs; - - startSetupCommandBuffer(); - - if (useStaging) - { - // Load all available mip levels into linear textures - // and copy to optimal tiling target - struct MipLevel { - VkImage image; - VkDeviceMemory memory; - }; - std::vector mipLevels; - mipLevels.resize(texture->mipLevels); - - // Copy mip levels - for (uint32_t level = 0; level < texture->mipLevels; ++level) - { - imageCreateInfo.extent.width = tex2D[level].dimensions().x; - imageCreateInfo.extent.height = tex2D[level].dimensions().y; - imageCreateInfo.extent.depth = 1; - - err = vkCreateImage(device, &imageCreateInfo, nullptr, &mipLevels[level].image); - assert(!err); - - vkGetImageMemoryRequirements(device, mipLevels[level].image, &memReqs); - memAllocInfo.allocationSize = memReqs.size; - getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memAllocInfo.memoryTypeIndex); - err = vkAllocateMemory(device, &memAllocInfo, nullptr, &mipLevels[level].memory); - assert(!err); - err = vkBindImageMemory(device, mipLevels[level].image, mipLevels[level].memory, 0); - assert(!err); - - VkImageSubresource subRes = {}; - subRes.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - - VkSubresourceLayout subResLayout; - void *data; - - vkGetImageSubresourceLayout(device, mipLevels[level].image, &subRes, &subResLayout); - assert(!err); - err = vkMapMemory(device, mipLevels[level].memory, 0, memReqs.size, 0, &data); - assert(!err); - size_t levelSize = tex2D[level].size(); - memcpy(data, tex2D[level].data(), levelSize); - vkUnmapMemory(device, mipLevels[level].memory); - - // Image barrier for linear image (base) - // Linear image will be used as a source for the copy - vkTools::setImageLayout( - setupCmdBuffer, - mipLevels[level].image, - VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); - } - - // Setup texture as blit target with optimal tiling - imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; - imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; - imageCreateInfo.mipLevels = texture->mipLevels; - imageCreateInfo.extent = { texture->width, texture->height, 1 }; - - err = vkCreateImage(device, &imageCreateInfo, nullptr, &texture->image); - assert(!err); - - vkGetImageMemoryRequirements(device, texture->image, &memReqs); - - memAllocInfo.allocationSize = memReqs.size; - - getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memAllocInfo.memoryTypeIndex); - err = vkAllocateMemory(device, &memAllocInfo, nullptr, &texture->deviceMemory); - assert(!err); - err = vkBindImageMemory(device, texture->image, texture->deviceMemory, 0); - assert(!err); - - // Image barrier for optimal image (target) - // Optimal image will be used as destination for the copy - vkTools::setImageLayout( - setupCmdBuffer, - texture->image, - VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); - - // Copy mip levels one by one - for (uint32_t level = 0; level < texture->mipLevels; ++level) - { - // Copy region for image blit - VkImageCopy copyRegion = {}; - - copyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - copyRegion.srcSubresource.baseArrayLayer = 0; - copyRegion.srcSubresource.mipLevel = 0; - copyRegion.srcSubresource.layerCount = 1; - copyRegion.srcOffset = { 0, 0, 0 }; - - copyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - copyRegion.dstSubresource.baseArrayLayer = 0; - // Set mip level to copy the linear image to - copyRegion.dstSubresource.mipLevel = level; - copyRegion.dstSubresource.layerCount = 1; - copyRegion.dstOffset = { 0, 0, 0 }; - - copyRegion.extent.width = tex2D[level].dimensions().x; - copyRegion.extent.height = tex2D[level].dimensions().y; - copyRegion.extent.depth = 1; - - // Put image copy into command buffer - vkCmdCopyImage( - setupCmdBuffer, - mipLevels[level].image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - texture->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, ©Region); - - // Change texture image layout to shader read after the copy - texture->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - vkTools::setImageLayout( - setupCmdBuffer, - texture->image, - VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - texture->imageLayout); - } - - flushSetupCommandBuffer(); - - // Clean up linear images - // No longer required after mip levels - // have been transformed over to optimal tiling - for (auto& level : mipLevels) - { - vkDestroyImage(device, level.image, nullptr); - vkFreeMemory(device, level.memory, nullptr); - } - } - else - { - // Prefer using optimal tiling, as linear tiling - // may support only a small set of features - // depending on implementation (e.g. no mip maps, only one layer, etc.) - - VkImage mappableImage; - VkDeviceMemory mappableMemory; - - // Load mip map level 0 to linear tiling image - err = vkCreateImage(device, &imageCreateInfo, nullptr, &mappableImage); - assert(!err); - - // Get memory requirements for this image - // like size and alignment - vkGetImageMemoryRequirements(device, mappableImage, &memReqs); - // Set memory allocation size to required memory size - memAllocInfo.allocationSize = memReqs.size; - - // Get memory type that can be mapped to host memory - getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memAllocInfo.memoryTypeIndex); - - // Allocate host memory - err = vkAllocateMemory(device, &memAllocInfo, nullptr, &mappableMemory); - assert(!err); - - // Bind allocated image for use - err = vkBindImageMemory(device, mappableImage, mappableMemory, 0); - assert(!err); - - // Get sub resource layout - // Mip map count, array layer, etc. - VkImageSubresource subRes = {}; - subRes.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - - VkSubresourceLayout subResLayout; - void *data; - - // Get sub resources layout - // Includes row pitch, size offsets, etc. - vkGetImageSubresourceLayout(device, mappableImage, &subRes, &subResLayout); - assert(!err); - - // Map image memory - err = vkMapMemory(device, mappableMemory, 0, memReqs.size, 0, &data); - assert(!err); - - // Copy image data into memory - memcpy(data, tex2D[subRes.mipLevel].data(), tex2D[subRes.mipLevel].size()); - - vkUnmapMemory(device, mappableMemory); - - // Linear tiled images don't need to be staged - // and can be directly used as textures - texture->image = mappableImage; - texture->deviceMemory = mappableMemory; - texture->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - - // Setup image memory barrier - vkTools::setImageLayout( - setupCmdBuffer, - texture->image, - VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, - texture->imageLayout); - - flushSetupCommandBuffer(); - } - - - // Create sampler - // In Vulkan textures are accessed by samplers - // This separates all the sampling information from the - // texture data - // This means you could have multiple sampler objects - // for the same texture with different settings - // Similar to the samplers available with OpenGL 3.3 - VkSamplerCreateInfo sampler = vkTools::initializers::samplerCreateInfo(); - sampler.magFilter = VK_FILTER_LINEAR; - sampler.minFilter = VK_FILTER_LINEAR; - sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; - sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - sampler.addressModeV = sampler.addressModeU; - sampler.addressModeW = sampler.addressModeU; - sampler.mipLodBias = 0.0f; - sampler.compareOp = VK_COMPARE_OP_NEVER; - sampler.minLod = 0.0f; - // Max level-of-detail should match mip level count - sampler.maxLod = (useStaging) ? (float)texture->mipLevels : 0.0f; - // Enable anisotropic filtering - sampler.maxAnisotropy = 8; - sampler.anisotropyEnable = VK_TRUE; - sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; - err = vkCreateSampler(device, &sampler, nullptr, &texture->sampler); - assert(!err); - - // Create image view - // Textures are not directly accessed by the shaders and - // are abstracted by image views containing additional - // information and sub resource ranges - VkImageViewCreateInfo view = vkTools::initializers::imageViewCreateInfo(); - view.image = VK_NULL_HANDLE; - view.viewType = VK_IMAGE_VIEW_TYPE_2D; - view.format = format; - view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; - view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - view.subresourceRange.baseMipLevel = 0; - view.subresourceRange.baseArrayLayer = 0; - view.subresourceRange.layerCount = 1; - // Linear tiling usually won't support mip maps - // Only set mip map count if optimal tiling is used - view.subresourceRange.levelCount = (useStaging) ? texture->mipLevels : 1; - view.image = texture->image; - err = vkCreateImageView(device, &view, nullptr, &texture->view); - assert(!err); - } - - // Free staging resources used while creating a texture - void destroyTextureImage(Texture *texture) - { - vkDestroyImage(device, texture->image, nullptr); - vkFreeMemory(device, texture->deviceMemory, nullptr); - } - - void startTiming() - { - tStart = std::chrono::high_resolution_clock::now(); - } - - void endTiming() - { - auto tEnd = std::chrono::high_resolution_clock::now(); - auto tDiff = std::chrono::duration(tEnd - tStart).count(); - frameTimer = (float)tDiff; - } - -}; \ No newline at end of file diff --git a/android/build-computeparticles.bat b/android/build-computeparticles.bat new file mode 100644 index 00000000..402e56c7 --- /dev/null +++ b/android/build-computeparticles.bat @@ -0,0 +1 @@ +_build computeparticles %1 \ No newline at end of file diff --git a/android/computeparticles/.gitignore b/android/computeparticles/.gitignore new file mode 100644 index 00000000..7a5d249c --- /dev/null +++ b/android/computeparticles/.gitignore @@ -0,0 +1,10 @@ +/assets/ +/res/ +/bin/ +/libs/ +/obj/ +/build.xml +/local.properties +/project.properties +/proguard-project.txt +*.apk \ No newline at end of file diff --git a/android/computeparticles/AndroidManifest.xml b/android/computeparticles/AndroidManifest.xml new file mode 100644 index 00000000..fbb981fa --- /dev/null +++ b/android/computeparticles/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/android/computeparticles/build.bat b/android/computeparticles/build.bat new file mode 100644 index 00000000..88059925 --- /dev/null +++ b/android/computeparticles/build.bat @@ -0,0 +1,20 @@ +cd jni +call ndk-build +if %ERRORLEVEL% EQU 0 ( + echo ndk-build has failed, build cancelled + cd.. + + mkdir "assets\shaders\computeparticles" + xcopy "..\..\data\shaders\computeparticles\*.spv" "assets\shaders\computeparticles" /Y + + mkdir "assets\textures" + xcopy "..\..\data\textures\particle01_rgba.ktx" "assets\textures" /Y + + mkdir "res\drawable" + xcopy "..\..\android\images\icon.png" "res\drawable" /Y + + call ant debug -Dout.final.file=vulkanComputeparticles.apk +) ELSE ( + echo error : ndk-build failed with errors! + cd.. +) diff --git a/android/computeparticles/computeparticles.NativeActivity/android_native_app_glue.c b/android/computeparticles/computeparticles.NativeActivity/android_native_app_glue.c deleted file mode 100644 index 6de5362a..00000000 --- a/android/computeparticles/computeparticles.NativeActivity/android_native_app_glue.c +++ /dev/null @@ -1,426 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__)) -#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__)) - -/* For debug builds, always enable the debug traces in this library */ -#ifndef NDEBUG -# define LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, "threaded_app", __VA_ARGS__)) -#else -# define LOGV(...) ((void)0) -#endif - -static void free_saved_state(struct android_app* android_app) { - pthread_mutex_lock(&android_app->mutex); - if (android_app->savedState != NULL) { - free(android_app->savedState); - android_app->savedState = NULL; - android_app->savedStateSize = 0; - } - pthread_mutex_unlock(&android_app->mutex); -} - -int8_t android_app_read_cmd(struct android_app* android_app) { - int8_t cmd; - if (read(android_app->msgread, &cmd, sizeof(cmd)) == sizeof(cmd)) { - switch (cmd) { - case APP_CMD_SAVE_STATE: - free_saved_state(android_app); - break; - } - return cmd; - } else { - LOGE("No data on command pipe!"); - } - return -1; -} - -static void print_cur_config(struct android_app* android_app) { - char lang[2], country[2]; - AConfiguration_getLanguage(android_app->config, lang); - AConfiguration_getCountry(android_app->config, country); - - LOGV("Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d " - "keys=%d nav=%d keysHid=%d navHid=%d sdk=%d size=%d long=%d " - "modetype=%d modenight=%d", - AConfiguration_getMcc(android_app->config), - AConfiguration_getMnc(android_app->config), - lang[0], lang[1], country[0], country[1], - AConfiguration_getOrientation(android_app->config), - AConfiguration_getTouchscreen(android_app->config), - AConfiguration_getDensity(android_app->config), - AConfiguration_getKeyboard(android_app->config), - AConfiguration_getNavigation(android_app->config), - AConfiguration_getKeysHidden(android_app->config), - AConfiguration_getNavHidden(android_app->config), - AConfiguration_getSdkVersion(android_app->config), - AConfiguration_getScreenSize(android_app->config), - AConfiguration_getScreenLong(android_app->config), - AConfiguration_getUiModeType(android_app->config), - AConfiguration_getUiModeNight(android_app->config)); -} - -void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd) { - switch (cmd) { - case APP_CMD_INPUT_CHANGED: - LOGV("APP_CMD_INPUT_CHANGED\n"); - pthread_mutex_lock(&android_app->mutex); - if (android_app->inputQueue != NULL) { - AInputQueue_detachLooper(android_app->inputQueue); - } - android_app->inputQueue = android_app->pendingInputQueue; - if (android_app->inputQueue != NULL) { - LOGV("Attaching input queue to looper"); - AInputQueue_attachLooper(android_app->inputQueue, - android_app->looper, LOOPER_ID_INPUT, NULL, - &android_app->inputPollSource); - } - pthread_cond_broadcast(&android_app->cond); - pthread_mutex_unlock(&android_app->mutex); - break; - - case APP_CMD_INIT_WINDOW: - LOGV("APP_CMD_INIT_WINDOW\n"); - pthread_mutex_lock(&android_app->mutex); - android_app->window = android_app->pendingWindow; - pthread_cond_broadcast(&android_app->cond); - pthread_mutex_unlock(&android_app->mutex); - break; - - case APP_CMD_TERM_WINDOW: - LOGV("APP_CMD_TERM_WINDOW\n"); - pthread_cond_broadcast(&android_app->cond); - break; - - case APP_CMD_RESUME: - case APP_CMD_START: - case APP_CMD_PAUSE: - case APP_CMD_STOP: - LOGV("activityState=%d\n", cmd); - pthread_mutex_lock(&android_app->mutex); - android_app->activityState = cmd; - pthread_cond_broadcast(&android_app->cond); - pthread_mutex_unlock(&android_app->mutex); - break; - - case APP_CMD_CONFIG_CHANGED: - LOGV("APP_CMD_CONFIG_CHANGED\n"); - AConfiguration_fromAssetManager(android_app->config, - android_app->activity->assetManager); - print_cur_config(android_app); - break; - - case APP_CMD_DESTROY: - LOGV("APP_CMD_DESTROY\n"); - android_app->destroyRequested = 1; - break; - } -} - -void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd) { - switch (cmd) { - case APP_CMD_TERM_WINDOW: - LOGV("APP_CMD_TERM_WINDOW\n"); - pthread_mutex_lock(&android_app->mutex); - android_app->window = NULL; - pthread_cond_broadcast(&android_app->cond); - pthread_mutex_unlock(&android_app->mutex); - break; - - case APP_CMD_SAVE_STATE: - LOGV("APP_CMD_SAVE_STATE\n"); - pthread_mutex_lock(&android_app->mutex); - android_app->stateSaved = 1; - pthread_cond_broadcast(&android_app->cond); - pthread_mutex_unlock(&android_app->mutex); - break; - - case APP_CMD_RESUME: - free_saved_state(android_app); - break; - } -} - -static void android_app_destroy(struct android_app* android_app) { - LOGV("android_app_destroy!"); - free_saved_state(android_app); - pthread_mutex_lock(&android_app->mutex); - if (android_app->inputQueue != NULL) { - AInputQueue_detachLooper(android_app->inputQueue); - } - AConfiguration_delete(android_app->config); - android_app->destroyed = 1; - pthread_cond_broadcast(&android_app->cond); - pthread_mutex_unlock(&android_app->mutex); - // Can't touch android_app object after this. -} - -static void process_input(struct android_app* app, struct android_poll_source* source) { - AInputEvent* event = NULL; - while (AInputQueue_getEvent(app->inputQueue, &event) >= 0) { - LOGV("New input event: type=%d\n", AInputEvent_getType(event)); - if (AInputQueue_preDispatchEvent(app->inputQueue, event)) { - continue; - } - int32_t handled = 0; - if (app->onInputEvent != NULL) handled = app->onInputEvent(app, event); - AInputQueue_finishEvent(app->inputQueue, event, handled); - } -} - -static void process_cmd(struct android_app* app, struct android_poll_source* source) { - int8_t cmd = android_app_read_cmd(app); - android_app_pre_exec_cmd(app, cmd); - if (app->onAppCmd != NULL) app->onAppCmd(app, cmd); - android_app_post_exec_cmd(app, cmd); -} - -static void* android_app_entry(void* param) { - struct android_app* android_app = (struct android_app*)param; - - android_app->config = AConfiguration_new(); - AConfiguration_fromAssetManager(android_app->config, android_app->activity->assetManager); - - print_cur_config(android_app); - - android_app->cmdPollSource.id = LOOPER_ID_MAIN; - android_app->cmdPollSource.app = android_app; - android_app->cmdPollSource.process = process_cmd; - android_app->inputPollSource.id = LOOPER_ID_INPUT; - android_app->inputPollSource.app = android_app; - android_app->inputPollSource.process = process_input; - - ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); - ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL, - &android_app->cmdPollSource); - android_app->looper = looper; - - pthread_mutex_lock(&android_app->mutex); - android_app->running = 1; - pthread_cond_broadcast(&android_app->cond); - pthread_mutex_unlock(&android_app->mutex); - - android_main(android_app); - - android_app_destroy(android_app); - return NULL; -} - -// -------------------------------------------------------------------- -// Native activity interaction (called from main thread) -// -------------------------------------------------------------------- - -static struct android_app* android_app_create(ANativeActivity* activity, - void* savedState, size_t savedStateSize) { - struct android_app* android_app = (struct android_app*)malloc(sizeof(struct android_app)); - memset(android_app, 0, sizeof(struct android_app)); - android_app->activity = activity; - - pthread_mutex_init(&android_app->mutex, NULL); - pthread_cond_init(&android_app->cond, NULL); - - if (savedState != NULL) { - android_app->savedState = malloc(savedStateSize); - android_app->savedStateSize = savedStateSize; - memcpy(android_app->savedState, savedState, savedStateSize); - } - - int msgpipe[2]; - if (pipe(msgpipe)) { - LOGE("could not create pipe: %s", strerror(errno)); - return NULL; - } - android_app->msgread = msgpipe[0]; - android_app->msgwrite = msgpipe[1]; - - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_create(&android_app->thread, &attr, android_app_entry, android_app); - - // Wait for thread to start. - pthread_mutex_lock(&android_app->mutex); - while (!android_app->running) { - pthread_cond_wait(&android_app->cond, &android_app->mutex); - } - pthread_mutex_unlock(&android_app->mutex); - - return android_app; -} - -static void android_app_write_cmd(struct android_app* android_app, int8_t cmd) { - if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) { - LOGE("Failure writing android_app cmd: %s\n", strerror(errno)); - } -} - -static void android_app_set_input(struct android_app* android_app, AInputQueue* inputQueue) { - pthread_mutex_lock(&android_app->mutex); - android_app->pendingInputQueue = inputQueue; - android_app_write_cmd(android_app, APP_CMD_INPUT_CHANGED); - while (android_app->inputQueue != android_app->pendingInputQueue) { - pthread_cond_wait(&android_app->cond, &android_app->mutex); - } - pthread_mutex_unlock(&android_app->mutex); -} - -static void android_app_set_window(struct android_app* android_app, ANativeWindow* window) { - pthread_mutex_lock(&android_app->mutex); - if (android_app->pendingWindow != NULL) { - android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW); - } - android_app->pendingWindow = window; - if (window != NULL) { - android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW); - } - while (android_app->window != android_app->pendingWindow) { - pthread_cond_wait(&android_app->cond, &android_app->mutex); - } - pthread_mutex_unlock(&android_app->mutex); -} - -static void android_app_set_activity_state(struct android_app* android_app, int8_t cmd) { - pthread_mutex_lock(&android_app->mutex); - android_app_write_cmd(android_app, cmd); - while (android_app->activityState != cmd) { - pthread_cond_wait(&android_app->cond, &android_app->mutex); - } - pthread_mutex_unlock(&android_app->mutex); -} - -static void android_app_free(struct android_app* android_app) { - pthread_mutex_lock(&android_app->mutex); - android_app_write_cmd(android_app, APP_CMD_DESTROY); - while (!android_app->destroyed) { - pthread_cond_wait(&android_app->cond, &android_app->mutex); - } - pthread_mutex_unlock(&android_app->mutex); - - close(android_app->msgread); - close(android_app->msgwrite); - pthread_cond_destroy(&android_app->cond); - pthread_mutex_destroy(&android_app->mutex); - free(android_app); -} - -static void onDestroy(ANativeActivity* activity) { - LOGV("Destroy: %p\n", activity); - android_app_free((struct android_app*)activity->instance); -} - -static void onStart(ANativeActivity* activity) { - LOGV("Start: %p\n", activity); - android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_START); -} - -static void onResume(ANativeActivity* activity) { - LOGV("Resume: %p\n", activity); - android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_RESUME); -} - -static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) { - struct android_app* android_app = (struct android_app*)activity->instance; - void* savedState = NULL; - - LOGV("SaveInstanceState: %p\n", activity); - pthread_mutex_lock(&android_app->mutex); - android_app->stateSaved = 0; - android_app_write_cmd(android_app, APP_CMD_SAVE_STATE); - while (!android_app->stateSaved) { - pthread_cond_wait(&android_app->cond, &android_app->mutex); - } - - if (android_app->savedState != NULL) { - savedState = android_app->savedState; - *outLen = android_app->savedStateSize; - android_app->savedState = NULL; - android_app->savedStateSize = 0; - } - - pthread_mutex_unlock(&android_app->mutex); - - return savedState; -} - -static void onPause(ANativeActivity* activity) { - LOGV("Pause: %p\n", activity); - android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_PAUSE); -} - -static void onStop(ANativeActivity* activity) { - LOGV("Stop: %p\n", activity); - android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_STOP); -} - -static void onConfigurationChanged(ANativeActivity* activity) { - struct android_app* android_app = (struct android_app*)activity->instance; - LOGV("ConfigurationChanged: %p\n", activity); - android_app_write_cmd(android_app, APP_CMD_CONFIG_CHANGED); -} - -static void onLowMemory(ANativeActivity* activity) { - struct android_app* android_app = (struct android_app*)activity->instance; - LOGV("LowMemory: %p\n", activity); - android_app_write_cmd(android_app, APP_CMD_LOW_MEMORY); -} - -static void onWindowFocusChanged(ANativeActivity* activity, int focused) { - LOGV("WindowFocusChanged: %p -- %d\n", activity, focused); - android_app_write_cmd((struct android_app*)activity->instance, - focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS); -} - -static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) { - LOGV("NativeWindowCreated: %p -- %p\n", activity, window); - android_app_set_window((struct android_app*)activity->instance, window); -} - -static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) { - LOGV("NativeWindowDestroyed: %p -- %p\n", activity, window); - android_app_set_window((struct android_app*)activity->instance, NULL); -} - -static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) { - LOGV("InputQueueCreated: %p -- %p\n", activity, queue); - android_app_set_input((struct android_app*)activity->instance, queue); -} - -static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) { - LOGV("InputQueueDestroyed: %p -- %p\n", activity, queue); - android_app_set_input((struct android_app*)activity->instance, NULL); -} - -void ANativeActivity_onCreate(ANativeActivity* activity, - void* savedState, size_t savedStateSize) { - LOGV("Creating: %p\n", activity); - activity->callbacks->onDestroy = onDestroy; - activity->callbacks->onStart = onStart; - activity->callbacks->onResume = onResume; - activity->callbacks->onSaveInstanceState = onSaveInstanceState; - activity->callbacks->onPause = onPause; - activity->callbacks->onStop = onStop; - activity->callbacks->onConfigurationChanged = onConfigurationChanged; - activity->callbacks->onLowMemory = onLowMemory; - activity->callbacks->onWindowFocusChanged = onWindowFocusChanged; - activity->callbacks->onNativeWindowCreated = onNativeWindowCreated; - activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed; - activity->callbacks->onInputQueueCreated = onInputQueueCreated; - activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed; - - activity->instance = android_app_create(activity, savedState, savedStateSize); -} diff --git a/android/computeparticles/computeparticles.NativeActivity/android_native_app_glue.h b/android/computeparticles/computeparticles.NativeActivity/android_native_app_glue.h deleted file mode 100644 index 1b390c2d..00000000 --- a/android/computeparticles/computeparticles.NativeActivity/android_native_app_glue.h +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#ifndef _ANDROID_NATIVE_APP_GLUE_H -#define _ANDROID_NATIVE_APP_GLUE_H - -#include -#include -#include - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * The native activity interface provided by - * is based on a set of application-provided callbacks that will be called - * by the Activity's main thread when certain events occur. - * - * This means that each one of this callbacks _should_ _not_ block, or they - * risk having the system force-close the application. This programming - * model is direct, lightweight, but constraining. - * - * The 'threaded_native_app' static library is used to provide a different - * execution model where the application can implement its own main event - * loop in a different thread instead. Here's how it works: - * - * 1/ The application must provide a function named "android_main()" that - * will be called when the activity is created, in a new thread that is - * distinct from the activity's main thread. - * - * 2/ android_main() receives a pointer to a valid "android_app" structure - * that contains references to other important objects, e.g. the - * ANativeActivity obejct instance the application is running in. - * - * 3/ the "android_app" object holds an ALooper instance that already - * listens to two important things: - * - * - activity lifecycle events (e.g. "pause", "resume"). See APP_CMD_XXX - * declarations below. - * - * - input events coming from the AInputQueue attached to the activity. - * - * Each of these correspond to an ALooper identifier returned by - * ALooper_pollOnce with values of LOOPER_ID_MAIN and LOOPER_ID_INPUT, - * respectively. - * - * Your application can use the same ALooper to listen to additional - * file-descriptors. They can either be callback based, or with return - * identifiers starting with LOOPER_ID_USER. - * - * 4/ Whenever you receive a LOOPER_ID_MAIN or LOOPER_ID_INPUT event, - * the returned data will point to an android_poll_source structure. You - * can call the process() function on it, and fill in android_app->onAppCmd - * and android_app->onInputEvent to be called for your own processing - * of the event. - * - * Alternatively, you can call the low-level functions to read and process - * the data directly... look at the process_cmd() and process_input() - * implementations in the glue to see how to do this. - * - * See the sample named "native-activity" that comes with the NDK with a - * full usage example. Also look at the JavaDoc of NativeActivity. - */ - -struct android_app; - -/** - * Data associated with an ALooper fd that will be returned as the "outData" - * when that source has data ready. - */ -struct android_poll_source { - // The identifier of this source. May be LOOPER_ID_MAIN or - // LOOPER_ID_INPUT. - int32_t id; - - // The android_app this ident is associated with. - struct android_app* app; - - // Function to call to perform the standard processing of data from - // this source. - void (*process)(struct android_app* app, struct android_poll_source* source); -}; - -/** - * This is the interface for the standard glue code of a threaded - * application. In this model, the application's code is running - * in its own thread separate from the main thread of the process. - * It is not required that this thread be associated with the Java - * VM, although it will need to be in order to make JNI calls any - * Java objects. - */ -struct android_app { - // The application can place a pointer to its own state object - // here if it likes. - void* userData; - - // Fill this in with the function to process main app commands (APP_CMD_*) - void (*onAppCmd)(struct android_app* app, int32_t cmd); - - // Fill this in with the function to process input events. At this point - // the event has already been pre-dispatched, and it will be finished upon - // return. Return 1 if you have handled the event, 0 for any default - // dispatching. - int32_t (*onInputEvent)(struct android_app* app, AInputEvent* event); - - // The ANativeActivity object instance that this app is running in. - ANativeActivity* activity; - - // The current configuration the app is running in. - AConfiguration* config; - - // This is the last instance's saved state, as provided at creation time. - // It is NULL if there was no state. You can use this as you need; the - // memory will remain around until you call android_app_exec_cmd() for - // APP_CMD_RESUME, at which point it will be freed and savedState set to NULL. - // These variables should only be changed when processing a APP_CMD_SAVE_STATE, - // at which point they will be initialized to NULL and you can malloc your - // state and place the information here. In that case the memory will be - // freed for you later. - void* savedState; - size_t savedStateSize; - - // The ALooper associated with the app's thread. - ALooper* looper; - - // When non-NULL, this is the input queue from which the app will - // receive user input events. - AInputQueue* inputQueue; - - // When non-NULL, this is the window surface that the app can draw in. - ANativeWindow* window; - - // Current content rectangle of the window; this is the area where the - // window's content should be placed to be seen by the user. - ARect contentRect; - - // Current state of the app's activity. May be either APP_CMD_START, - // APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP; see below. - int activityState; - - // This is non-zero when the application's NativeActivity is being - // destroyed and waiting for the app thread to complete. - int destroyRequested; - - // ------------------------------------------------- - // Below are "private" implementation of the glue code. - - pthread_mutex_t mutex; - pthread_cond_t cond; - - int msgread; - int msgwrite; - - pthread_t thread; - - struct android_poll_source cmdPollSource; - struct android_poll_source inputPollSource; - - int running; - int stateSaved; - int destroyed; - int redrawNeeded; - AInputQueue* pendingInputQueue; - ANativeWindow* pendingWindow; - ARect pendingContentRect; -}; - -enum { - /** - * Looper data ID of commands coming from the app's main thread, which - * is returned as an identifier from ALooper_pollOnce(). The data for this - * identifier is a pointer to an android_poll_source structure. - * These can be retrieved and processed with android_app_read_cmd() - * and android_app_exec_cmd(). - */ - LOOPER_ID_MAIN = 1, - - /** - * Looper data ID of events coming from the AInputQueue of the - * application's window, which is returned as an identifier from - * ALooper_pollOnce(). The data for this identifier is a pointer to an - * android_poll_source structure. These can be read via the inputQueue - * object of android_app. - */ - LOOPER_ID_INPUT = 2, - - /** - * Start of user-defined ALooper identifiers. - */ - LOOPER_ID_USER = 3, -}; - -enum { - /** - * Command from main thread: the AInputQueue has changed. Upon processing - * this command, android_app->inputQueue will be updated to the new queue - * (or NULL). - */ - APP_CMD_INPUT_CHANGED, - - /** - * Command from main thread: a new ANativeWindow is ready for use. Upon - * receiving this command, android_app->window will contain the new window - * surface. - */ - APP_CMD_INIT_WINDOW, - - /** - * Command from main thread: the existing ANativeWindow needs to be - * terminated. Upon receiving this command, android_app->window still - * contains the existing window; after calling android_app_exec_cmd - * it will be set to NULL. - */ - APP_CMD_TERM_WINDOW, - - /** - * Command from main thread: the current ANativeWindow has been resized. - * Please redraw with its new size. - */ - APP_CMD_WINDOW_RESIZED, - - /** - * Command from main thread: the system needs that the current ANativeWindow - * be redrawn. You should redraw the window before handing this to - * android_app_exec_cmd() in order to avoid transient drawing glitches. - */ - APP_CMD_WINDOW_REDRAW_NEEDED, - - /** - * Command from main thread: the content area of the window has changed, - * such as from the soft input window being shown or hidden. You can - * find the new content rect in android_app::contentRect. - */ - APP_CMD_CONTENT_RECT_CHANGED, - - /** - * Command from main thread: the app's activity window has gained - * input focus. - */ - APP_CMD_GAINED_FOCUS, - - /** - * Command from main thread: the app's activity window has lost - * input focus. - */ - APP_CMD_LOST_FOCUS, - - /** - * Command from main thread: the current device configuration has changed. - */ - APP_CMD_CONFIG_CHANGED, - - /** - * Command from main thread: the system is running low on memory. - * Try to reduce your memory use. - */ - APP_CMD_LOW_MEMORY, - - /** - * Command from main thread: the app's activity has been started. - */ - APP_CMD_START, - - /** - * Command from main thread: the app's activity has been resumed. - */ - APP_CMD_RESUME, - - /** - * Command from main thread: the app should generate a new saved state - * for itself, to restore from later if needed. If you have saved state, - * allocate it with malloc and place it in android_app.savedState with - * the size in android_app.savedStateSize. The will be freed for you - * later. - */ - APP_CMD_SAVE_STATE, - - /** - * Command from main thread: the app's activity has been paused. - */ - APP_CMD_PAUSE, - - /** - * Command from main thread: the app's activity has been stopped. - */ - APP_CMD_STOP, - - /** - * Command from main thread: the app's activity is being destroyed, - * and waiting for the app thread to clean up and exit before proceeding. - */ - APP_CMD_DESTROY, -}; - -/** - * Call when ALooper_pollAll() returns LOOPER_ID_MAIN, reading the next - * app command message. - */ -int8_t android_app_read_cmd(struct android_app* android_app); - -/** - * Call with the command returned by android_app_read_cmd() to do the - * initial pre-processing of the given command. You can perform your own - * actions for the command after calling this function. - */ -void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd); - -/** - * Call with the command returned by android_app_read_cmd() to do the - * final post-processing of the given command. You must have done your own - * actions for the command before calling this function. - */ -void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd); - -/** - * This is the function that application code must implement, representing - * the main entry to the app. - */ -extern void android_main(struct android_app* app); - -#ifdef __cplusplus -} -#endif - -#endif /* _ANDROID_NATIVE_APP_GLUE_H */ diff --git a/android/computeparticles/computeparticles.NativeActivity/computeparticles.NativeActivity.vcxproj b/android/computeparticles/computeparticles.NativeActivity/computeparticles.NativeActivity.vcxproj deleted file mode 100644 index 19b1070c..00000000 --- a/android/computeparticles/computeparticles.NativeActivity/computeparticles.NativeActivity.vcxproj +++ /dev/null @@ -1,224 +0,0 @@ - - - - - Debug - ARM - - - Release - ARM - - - Debug - ARM64 - - - Release - ARM64 - - - Debug - x64 - - - Release - x64 - - - Debug - x86 - - - Release - x86 - - - - {1C538546-0768-4071-81A6-86EE8F33AA1A} - Android - computeparticles - en-US - 14.0 - Android - 1.0 - - - - DynamicLibrary - true - Clang_3_6 - - - DynamicLibrary - false - Clang_3_6 - - - DynamicLibrary - true - Clang_3_6 - - - DynamicLibrary - false - Clang_3_6 - - - DynamicLibrary - true - Clang_3_6 - - - DynamicLibrary - false - Clang_3_6 - - - DynamicLibrary - true - Clang_3_6 - - - DynamicLibrary - false - Clang_3_6 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Use - pch.h - CompileAsCpp - - - %(LibraryDependencies);GLESv1_CM;EGL; - - - - - Use - pch.h - CompileAsCpp - - - %(LibraryDependencies);GLESv1_CM;EGL; - - - - - Use - pch.h - CompileAsCpp - VK_NO_PROTOTYPES;VK_USE_PLATFORM_ANDROID_KHR;__STDINT_LIMITS - c++11 - ../../../external;../../../base;../../../external/gli;../../../external/glm;../../base;%(AdditionalIncludeDirectories) - - - %(LibraryDependencies);GLESv1_CM;EGL; - -lm %(AdditionalOptions) - - - - - Use - pch.h - CompileAsCpp - - - %(LibraryDependencies);GLESv1_CM;EGL; - - - - - Use - pch.h - CompileAsCpp - - - %(LibraryDependencies);GLESv1_CM;EGL; - - - - - Use - pch.h - CompileAsCpp - - - %(LibraryDependencies);GLESv1_CM;EGL; - - - - - Use - pch.h - CompileAsCpp - - - %(LibraryDependencies);GLESv1_CM;EGL; - - - - - Use - pch.h - CompileAsCpp - - - %(LibraryDependencies);GLESv1_CM;EGL; - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/computeparticles/computeparticles.NativeActivity/computeparticles.NativeActivity.vcxproj.filters b/android/computeparticles/computeparticles.NativeActivity/computeparticles.NativeActivity.vcxproj.filters deleted file mode 100644 index 6ecf4947..00000000 --- a/android/computeparticles/computeparticles.NativeActivity/computeparticles.NativeActivity.vcxproj.filters +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/computeparticles/computeparticles.NativeActivity/computeparticles.cpp b/android/computeparticles/computeparticles.NativeActivity/computeparticles.cpp deleted file mode 100644 index 3b46e31c..00000000 --- a/android/computeparticles/computeparticles.NativeActivity/computeparticles.cpp +++ /dev/null @@ -1,822 +0,0 @@ -/* -* Vulkan Example - Compute shader particle system -* -* Note : -* This is a basic android example. It may be integrated into the other examples at some point in the future. -* Until then this serves as a starting point for using Vulkan on Android, with some of the functionality required -* already moved to the example base classes (e.g. swap chain) -* -* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de -* -* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) -*/ - -#include -#include "vulkanandroid.h" -#include "vulkanswapchain.hpp" -#include "vulkanandroidbase.hpp" -#include - -#define GLM_FORCE_RADIANS -#define GLM_FORCE_DEPTH_ZERO_TO_ONE -#include "glm/glm.hpp" -#include "glm/gtc/matrix_transform.hpp" - -#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "AndroidProject1.NativeActivity", __VA_ARGS__)) -#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "AndroidProject1.NativeActivity", __VA_ARGS__)) - -#define VERTEX_BUFFER_BIND_ID 0 -#define PARTICLE_COUNT 4 * 1024 - -struct saved_state { - glm::vec3 rotation; - float zoom; -}; - -struct VulkanExample : public VulkanAndroidExampleBase -{ -public: - int animating; - struct saved_state state; - - float timer = 0.0f; - float animStart = 50.0f; - bool animate = true; - - // Vulkan - struct Vertex { - float pos[3]; - float uv[2]; - }; - - Texture texture; - - VkDescriptorSetLayout descriptorSetLayout; - VkDescriptorSet descriptorSet; - VkPipelineLayout pipelineLayout; - - struct { - VkPipelineVertexInputStateCreateInfo inputState; - std::vector bindingDescriptions; - std::vector attributeDescriptions; - } vertices; - - struct { - VkPipeline solid; - VkPipeline compute; - } pipelines; - - VkQueue computeQueue; - VkCommandBuffer computeCmdBuffer; - VkPipelineLayout computePipelineLayout; - VkDescriptorSet computeDescriptorSet; - VkDescriptorSetLayout computeDescriptorSetLayout; - - vkTools::UniformData computeStorageBuffer; - - struct Particle { - glm::vec4 pos; - glm::vec4 col; - glm::vec4 vel; - }; - - struct { - float deltaT; - float destX; - float destY; - int32_t particleCount = PARTICLE_COUNT; - } computeUbo; - - vkTools::UniformData uniformDataCompute; - - void initVulkan() - { - VulkanAndroidExampleBase::initVulkan(); - - loadTexture( - "textures/android_robot.ktx", - VK_FORMAT_R8G8B8A8_UNORM, - &texture, - false); - - createCommandBuffers(); - - // Compute stuff - getComputeQueue(); - createComputeCommandBuffer(); - prepareStorageBuffers(); - - prepareUniformBuffers(); - setupDescriptorSetLayout(); - preparePipelines(); - setupDescriptorPool(); - setupDescriptorSet(); - - prepareCompute(); - - buildCommandBuffers(); - buildComputeCommandBuffer(); - - state.zoom = -5.0f; - state.rotation = glm::vec3(); - - prepared = true; - } - - void cleanupVulkan() - { - prepared = false; - vkDestroyPipeline(device, pipelines.solid, nullptr); - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyPipelineLayout(device, computePipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - vkDestroyDescriptorSetLayout(device, computeDescriptorSetLayout, nullptr); - vkDestroyBuffer(device, uniformDataCompute.buffer, nullptr); - vkFreeMemory(device, uniformDataCompute.memory, nullptr); - vkDestroyBuffer(device, computeStorageBuffer.buffer, nullptr); - vkFreeMemory(device, computeStorageBuffer.memory, nullptr); - - destroyTextureImage(&texture); - - vkFreeCommandBuffers(device, cmdPool, 1, &computeCmdBuffer); - - VulkanExample::cleanUpVulkan(); - } - - // Find and create a compute capable device queue - void getComputeQueue() - { - uint32_t queueIndex = 0; - uint32_t queueCount; - vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, NULL); - assert(queueCount >= 1); - - std::vector queueProps; - queueProps.resize(queueCount); - vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, queueProps.data()); - - for (queueIndex = 0; queueIndex < queueCount; queueIndex++) - { - if (queueProps[queueIndex].queueFlags & VK_QUEUE_COMPUTE_BIT) - break; - } - assert(queueIndex < queueCount); - - VkDeviceQueueCreateInfo queueCreateInfo = {}; - queueCreateInfo.queueFamilyIndex = queueIndex; - queueCreateInfo.queueCount = 1; - vkGetDeviceQueue(device, queueIndex, 0, &computeQueue); - } - - void createComputeCommandBuffer() - { - VkCommandBufferAllocateInfo cmdBufAllocateInfo = - vkTools::initializers::commandBufferAllocateInfo( - cmdPool, - VK_COMMAND_BUFFER_LEVEL_PRIMARY, - 1); - - VkResult vkRes = vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &computeCmdBuffer); - assert(!vkRes); - } - - void buildComputeCommandBuffer() - { - VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo();; - - vkBeginCommandBuffer(computeCmdBuffer, &cmdBufInfo); - - vkCmdBindPipeline(computeCmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipelines.compute); - vkCmdBindDescriptorSets(computeCmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 0, 1, &computeDescriptorSet, 0, 0); - - vkCmdDispatch(computeCmdBuffer, PARTICLE_COUNT / 16, 1, 1); - - vkEndCommandBuffer(computeCmdBuffer); - } - - void updateUniformBuffers() - { - computeUbo.deltaT = (1.0f / frameTimer) * 0.15f; - computeUbo.destX = sin(glm::radians(timer*360.0)) * 0.75f; - computeUbo.destY = cos(glm::radians(timer*360.0)) * 0.10f; - uint8_t *pData; - VkResult err = vkMapMemory(device, uniformDataCompute.memory, 0, sizeof(computeUbo), 0, (void **)&pData); - assert(!err); - memcpy(pData, &computeUbo, sizeof(computeUbo)); - vkUnmapMemory(device, uniformDataCompute.memory); - } - - void prepareUniformBuffers() - { - // Prepare and initialize uniform buffer containing shader uniforms - VkMemoryRequirements memReqs; - - // Vertex shader uniform buffer block - VkBufferCreateInfo bufferInfo = {}; - VkMemoryAllocateInfo allocInfo = vkTools::initializers::memoryAllocateInfo(); - VkResult err; - - bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - bufferInfo.size = sizeof(computeUbo); - bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; - - err = vkCreateBuffer(device, &bufferInfo, nullptr, &uniformDataCompute.buffer); - assert(!err); - vkGetBufferMemoryRequirements(device, uniformDataCompute.buffer, &memReqs); - allocInfo.allocationSize = memReqs.size; - getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &allocInfo.memoryTypeIndex); - err = vkAllocateMemory(device, &allocInfo, nullptr, &(uniformDataCompute.memory)); - assert(!err); - err = vkBindBufferMemory(device, uniformDataCompute.buffer, uniformDataCompute.memory, 0); - assert(!err); - - uniformDataCompute.descriptor.buffer = uniformDataCompute.buffer; - uniformDataCompute.descriptor.offset = 0; - uniformDataCompute.descriptor.range = sizeof(computeUbo); - - updateUniformBuffers(); - } - - void preparePipelines() - { - VkResult err; - - VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = - vkTools::initializers::pipelineInputAssemblyStateCreateInfo( - VK_PRIMITIVE_TOPOLOGY_POINT_LIST, - 0, - VK_FALSE); - - VkPipelineRasterizationStateCreateInfo rasterizationState = - vkTools::initializers::pipelineRasterizationStateCreateInfo( - VK_POLYGON_MODE_FILL, - VK_CULL_MODE_NONE, - VK_FRONT_FACE_COUNTER_CLOCKWISE, - 0); - - VkPipelineColorBlendAttachmentState blendAttachmentState = - vkTools::initializers::pipelineColorBlendAttachmentState( - 0xf, - VK_FALSE); - - VkPipelineColorBlendStateCreateInfo colorBlendState = - vkTools::initializers::pipelineColorBlendStateCreateInfo( - 1, - &blendAttachmentState); - - VkPipelineDepthStencilStateCreateInfo depthStencilState = - vkTools::initializers::pipelineDepthStencilStateCreateInfo( - VK_TRUE, - VK_TRUE, - VK_COMPARE_OP_LESS_OR_EQUAL); - - VkPipelineViewportStateCreateInfo viewportState = - vkTools::initializers::pipelineViewportStateCreateInfo(1, 1, 0); - - VkPipelineMultisampleStateCreateInfo multisampleState = - vkTools::initializers::pipelineMultisampleStateCreateInfo( - VK_SAMPLE_COUNT_1_BIT, - 0); - - std::vector dynamicStateEnables; - dynamicStateEnables.push_back(VK_DYNAMIC_STATE_VIEWPORT); - dynamicStateEnables.push_back(VK_DYNAMIC_STATE_SCISSOR); - - VkPipelineDynamicStateCreateInfo dynamicState = - vkTools::initializers::pipelineDynamicStateCreateInfo( - dynamicStateEnables.data(), - dynamicStateEnables.size(), - 0); - - // Rendering pipeline - // Load shaders - std::array shaderStages; - - shaderStages[0] = loadShader("shaders/particle.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); - shaderStages[1] = loadShader("shaders/particle.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); - - VkGraphicsPipelineCreateInfo pipelineCreateInfo = - vkTools::initializers::pipelineCreateInfo( - pipelineLayout, - renderPass, - 0); - - pipelineCreateInfo.pVertexInputState = &vertices.inputState; - pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; - pipelineCreateInfo.pRasterizationState = &rasterizationState; - pipelineCreateInfo.pColorBlendState = &colorBlendState; - pipelineCreateInfo.pMultisampleState = &multisampleState; - pipelineCreateInfo.pViewportState = &viewportState; - pipelineCreateInfo.pDepthStencilState = &depthStencilState; - pipelineCreateInfo.pDynamicState = &dynamicState; - pipelineCreateInfo.stageCount = shaderStages.size(); - pipelineCreateInfo.pStages = shaderStages.data(); - pipelineCreateInfo.renderPass = renderPass; - - // Additive blending - blendAttachmentState.blendEnable = VK_TRUE; - blendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; - blendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD; - blendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; - blendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ONE; - blendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD; - blendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; - blendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA; - - - err = vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.solid); - assert(!err); - } - - // Setup and fill the compute shader storage buffers for - // vertex positions and velocities - void prepareStorageBuffers() - { - float destPosX = 0.0f; - float destPosY = 0.0f; - - // Initial particle positions - std::vector particleBuffer; - for (int i = 0; i < PARTICLE_COUNT; ++i) - { - // Position - float aspectRatio = (float)height / (float)width; - float rndVal = (float)rand() / (float)(RAND_MAX / (360.0f * 3.14f * 2.0f)); - float rndRad = (float)rand() / (float)(RAND_MAX)* 0.65f; - Particle p; - p.pos = glm::vec4( - destPosX + cos(rndVal) * rndRad * aspectRatio, - destPosY + sin(rndVal) * rndRad, - 0.0f, - 1.0f); - p.col = glm::vec4( - (float)(rand() % 255) / 255.0f, - (float)(rand() % 255) / 255.0f, - (float)(rand() % 255) / 255.0f, - 1.0f); - p.vel = glm::vec4(0.0f); - particleBuffer.push_back(p); - } - - // Buffer size is the same for all storage buffers - uint32_t storageBufferSize = particleBuffer.size() * sizeof(Particle); - - VkMemoryAllocateInfo memAlloc = vkTools::initializers::memoryAllocateInfo(); - VkMemoryRequirements memReqs; - - VkResult err; - void *data; - - // Allocate and fill storage buffer object - VkBufferCreateInfo vBufferInfo = - vkTools::initializers::bufferCreateInfo( - VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, - storageBufferSize); - err = vkCreateBuffer(device, &vBufferInfo, nullptr, &computeStorageBuffer.buffer); - assert(!err); - vkGetBufferMemoryRequirements(device, computeStorageBuffer.buffer, &memReqs); - memAlloc.allocationSize = memReqs.size; - getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memAlloc.memoryTypeIndex); - err = vkAllocateMemory(device, &memAlloc, nullptr, &computeStorageBuffer.memory); - assert(!err); - err = vkMapMemory(device, computeStorageBuffer.memory, 0, storageBufferSize, 0, &data); - assert(!err); - memcpy(data, particleBuffer.data(), storageBufferSize); - vkUnmapMemory(device, computeStorageBuffer.memory); - err = vkBindBufferMemory(device, computeStorageBuffer.buffer, computeStorageBuffer.memory, 0); - assert(!err); - - computeStorageBuffer.descriptor.buffer = computeStorageBuffer.buffer; - computeStorageBuffer.descriptor.offset = 0; - computeStorageBuffer.descriptor.range = storageBufferSize; - - // Binding description - vertices.bindingDescriptions.resize(1); - vertices.bindingDescriptions[0] = - vkTools::initializers::vertexInputBindingDescription( - VERTEX_BUFFER_BIND_ID, - sizeof(Particle), - VK_VERTEX_INPUT_RATE_VERTEX); - - // Attribute descriptions - // Describes memory layout and shader positions - vertices.attributeDescriptions.resize(2); - // Location 0 : Position - vertices.attributeDescriptions[0] = - vkTools::initializers::vertexInputAttributeDescription( - VERTEX_BUFFER_BIND_ID, - 0, - VK_FORMAT_R32G32B32A32_SFLOAT, - 0); - // Location 1 : Color - vertices.attributeDescriptions[1] = - vkTools::initializers::vertexInputAttributeDescription( - VERTEX_BUFFER_BIND_ID, - 1, - VK_FORMAT_R32G32B32A32_SFLOAT, - sizeof(float) * 4); - - // Assign to vertex buffer - vertices.inputState = vkTools::initializers::pipelineVertexInputStateCreateInfo(); - vertices.inputState.vertexBindingDescriptionCount = vertices.bindingDescriptions.size(); - vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data(); - vertices.inputState.vertexAttributeDescriptionCount = vertices.attributeDescriptions.size(); - vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data(); - } - - void prepareCompute() - { - // Create compute pipeline - // Compute pipelines are created separate from graphics pipelines - // even if they use the same queue - - std::vector setLayoutBindings; - setLayoutBindings.push_back( - // Binding 0 : Particle position storage buffer - vkTools::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - VK_SHADER_STAGE_COMPUTE_BIT, - 0)); - setLayoutBindings.push_back( - // Binding 1 : Uniform buffer - vkTools::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - VK_SHADER_STAGE_COMPUTE_BIT, - 1)); - - VkDescriptorSetLayoutCreateInfo descriptorLayout = - vkTools::initializers::descriptorSetLayoutCreateInfo( - setLayoutBindings.data(), - setLayoutBindings.size()); - - VkResult err = vkCreateDescriptorSetLayout( - device, - &descriptorLayout, - nullptr, - &computeDescriptorSetLayout); - assert(!err); - - VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = - vkTools::initializers::pipelineLayoutCreateInfo( - &computeDescriptorSetLayout, - 1); - - err = vkCreatePipelineLayout( - device, - &pPipelineLayoutCreateInfo, - nullptr, - &computePipelineLayout); - assert(!err); - - VkDescriptorSetAllocateInfo allocInfo = - vkTools::initializers::descriptorSetAllocateInfo( - descriptorPool, - &computeDescriptorSetLayout, - 1); - - err = vkAllocateDescriptorSets(device, &allocInfo, &computeDescriptorSet); - assert(!err); - - std::vector computeWriteDescriptorSets; - computeWriteDescriptorSets.push_back( - // Binding 0 : Particle position storage buffer - vkTools::initializers::writeDescriptorSet( - computeDescriptorSet, - VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - 0, - &computeStorageBuffer.descriptor)); - computeWriteDescriptorSets.push_back( - // Binding 1 : Uniform buffer - vkTools::initializers::writeDescriptorSet( - computeDescriptorSet, - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - 1, - &uniformDataCompute.descriptor)); - - vkUpdateDescriptorSets(device, computeWriteDescriptorSets.size(), computeWriteDescriptorSets.data(), 0, NULL); - - // Create pipeline - VkComputePipelineCreateInfo computePipelineCreateInfo = - vkTools::initializers::computePipelineCreateInfo( - computePipelineLayout, - 0); - computePipelineCreateInfo.stage = loadShader("shaders/particle.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT); - - err = vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &pipelines.compute); - assert(!err); - } - - void setupDescriptorPool() - { - std::vector poolSizes; - poolSizes.push_back(vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1)); - poolSizes.push_back(vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1)); - poolSizes.push_back(vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)); - - VkDescriptorPoolCreateInfo descriptorPoolInfo = - vkTools::initializers::descriptorPoolCreateInfo( - poolSizes.size(), - poolSizes.data(), - 2); - - VkResult vkRes = vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool); - assert(!vkRes); - } - - void setupDescriptorSetLayout() - { - std::vector setLayoutBindings; - setLayoutBindings.push_back( - // Binding 0 : Fragment shader image sampler - vkTools::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - VK_SHADER_STAGE_FRAGMENT_BIT, - 0)); - - VkDescriptorSetLayoutCreateInfo descriptorLayout = - vkTools::initializers::descriptorSetLayoutCreateInfo( - setLayoutBindings.data(), - setLayoutBindings.size()); - - VkResult err = vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout); - assert(!err); - - VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = - vkTools::initializers::pipelineLayoutCreateInfo( - &descriptorSetLayout, - 1); - - err = vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout); - assert(!err); - } - - void setupDescriptorSet() - { - VkDescriptorSetAllocateInfo allocInfo = - vkTools::initializers::descriptorSetAllocateInfo( - descriptorPool, - &descriptorSetLayout, - 1); - - VkResult vkRes = vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet); - assert(!vkRes); - - // Image descriptor for the color map texture - VkDescriptorImageInfo texDescriptor = - vkTools::initializers::descriptorImageInfo( - texture.sampler, - texture.view, - VK_IMAGE_LAYOUT_GENERAL); - - std::vector writeDescriptorSets; - writeDescriptorSets.push_back( - // Binding 0 : Fragment shader texture sampler - vkTools::initializers::writeDescriptorSet( - descriptorSet, - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - 0, - &texDescriptor)); - - vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL); - } - - void buildCommandBuffers() - { - VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo(); - - VkClearValue clearValues[2]; - clearValues[0].color = { { 0.0f, 0.0f, 0.0f, 0.0f } }; - clearValues[1].depthStencil = { 1.0f, 0 }; - - VkRenderPassBeginInfo renderPassBeginInfo = vkTools::initializers::renderPassBeginInfo(); - renderPassBeginInfo.renderPass = renderPass; - renderPassBeginInfo.renderArea.offset.x = 0; - renderPassBeginInfo.renderArea.offset.y = 0; - renderPassBeginInfo.renderArea.extent.width = width; - renderPassBeginInfo.renderArea.extent.height = height; - renderPassBeginInfo.clearValueCount = 2; - renderPassBeginInfo.pClearValues = clearValues; - - VkResult err; - - for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) - { - // Set target frame buffer - renderPassBeginInfo.framebuffer = frameBuffers[i]; - - err = vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo); - assert(!err); - - // Buffer memory barrier to make sure that compute shader - // writes are finished before using the storage buffer - // in the vertex shader - VkBufferMemoryBarrier bufferBarrier = vkTools::initializers::bufferMemoryBarrier(); - // Source access : Compute shader buffer write - bufferBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; - // Dest access : Vertex shader access (attribute binding) - bufferBarrier.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; - bufferBarrier.buffer = computeStorageBuffer.buffer; - bufferBarrier.offset = 0; - bufferBarrier.size = computeStorageBuffer.descriptor.range; - bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - - vkCmdPipelineBarrier( - drawCmdBuffers[i], - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_FLAGS_NONE, - 0, nullptr, - 1, &bufferBarrier, - 0, nullptr); - - vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); - - VkViewport viewport = vkTools::initializers::viewport( - (float)width, - (float)height, - 0.0f, - 1.0f - ); - vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); - - VkRect2D scissor = vkTools::initializers::rect2D( - width, - height, - 0, - 0 - ); - vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); - - vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL); - vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.solid); - - VkDeviceSize offsets[1] = { 0 }; - vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &computeStorageBuffer.buffer, offsets); - vkCmdDraw(drawCmdBuffers[i], PARTICLE_COUNT, 1, 0, 0); - - vkCmdEndRenderPass(drawCmdBuffers[i]); - - VkImageMemoryBarrier prePresentBarrier = vkTools::prePresentBarrier(swapChain.buffers[i].image); - vkCmdPipelineBarrier( - drawCmdBuffers[i], - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_FLAGS_NONE, - 0, nullptr, - 0, nullptr, - 1, &prePresentBarrier); - - err = vkEndCommandBuffer(drawCmdBuffers[i]); - assert(!err); - } - } - - void draw() - { - VkResult err; - - // Get next image in the swap chain (back/front buffer) - err = swapChain.acquireNextImage(semaphores.presentComplete, ¤tBuffer); - assert(!err); - - submitPostPresentBarrier(swapChain.buffers[currentBuffer].image); - - VkPipelineStageFlags pipelineStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; - VkSubmitInfo submitInfo = vkTools::initializers::submitInfo(); - submitInfo.waitSemaphoreCount = 1; - submitInfo.pWaitSemaphores = &semaphores.presentComplete; - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; - submitInfo.pWaitDstStageMask = &pipelineStages; - submitInfo.signalSemaphoreCount = 1; - submitInfo.pSignalSemaphores = &semaphores.submitSignal; - - // Submit to the graphics queue - err = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); - assert(!err); - - submitPrePresentBarrier(swapChain.buffers[currentBuffer].image); - - // Present the current buffer to the swap chain - // This will display the image - err = swapChain.queuePresent(queue, currentBuffer, semaphores.submitSignal); - assert(!err); - - // Compute - VkSubmitInfo computeSubmitInfo = vkTools::initializers::submitInfo(); - computeSubmitInfo.commandBufferCount = 1; - computeSubmitInfo.pCommandBuffers = &computeCmdBuffer; - - err = vkQueueSubmit(computeQueue, 1, &computeSubmitInfo, VK_NULL_HANDLE); - assert(!err); - - err = vkQueueWaitIdle(computeQueue); - assert(!err); - } - - void render() - { - // Render frame - if (prepared) - { - startTiming(); - if (animating) - { - if (animStart > 0.0f) - { - animStart -= 0.15f * (1.0f / frameTimer); - } - if ((animate) & (animStart <= 0.0f)) - { - timer += 0.5f * (1.0f / frameTimer); - if (timer > 1.0) - { - timer -= 1.0f; - } - } - updateUniformBuffers(); - } - draw(); - endTiming(); - } - } - -}; - -static int32_t handleInput(struct android_app* app, AInputEvent* event) -{ - struct VulkanExample* vulkanExample = (struct VulkanExample*)app->userData; - if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) - { - // todo - return 1; - } - return 0; -} - -static void handleCommand(struct android_app* app, int32_t cmd) -{ - VulkanExample* vulkanExample = (VulkanExample*)app->userData; - switch (cmd) - { - case 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: - if (vulkanExample->app->window != NULL) - { - vulkanExample->initVulkan(); - assert(vulkanExample->prepared); - } - break; - case APP_CMD_LOST_FOCUS: - vulkanExample->animating = 0; - break; - } -} - -/** -* This is the main entry point of a native application that is using -* android_native_app_glue. It runs in its own thread, with its own -* event loop for receiving input events and doing other things. -*/ -void android_main(struct android_app* state) -{ - VulkanExample *engine = new VulkanExample(); - - state->userData = engine; - state->onAppCmd = handleCommand; - state->onInputEvent = handleInput; - engine->app = state; - - engine->animating = 1; - - // loop waiting for stuff to do. - - while (1) - { - // Read all pending events. - int ident; - int events; - struct android_poll_source* source; - - while ((ident = ALooper_pollAll(engine->animating ? 0 : -1, NULL, &events, (void**)&source)) >= 0) - { - if (source != NULL) - { - source->process(state, source); - } - - if (state->destroyRequested != 0) - { - engine->cleanupVulkan(); - return; - } - } - - engine->render(); - } -} diff --git a/android/computeparticles/computeparticles.NativeActivity/pch.h b/android/computeparticles/computeparticles.NativeActivity/pch.h deleted file mode 100644 index 487822aa..00000000 --- a/android/computeparticles/computeparticles.NativeActivity/pch.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// pch.h -// Header for standard system include files. -// -// Used by the build system to generate the precompiled header. Note that no -// pch.cpp is needed and the pch.h is automatically included in all cpp files -// that are part of the project -// - -#include -#include - -#include -#include -#include - -#include "gli/gli.hpp" - -#include - -#include -#include "android_native_app_glue.h" diff --git a/android/computeparticles/computeparticles.Packaging/AndroidManifest.xml b/android/computeparticles/computeparticles.Packaging/AndroidManifest.xml deleted file mode 100644 index 61155b5f..00000000 --- a/android/computeparticles/computeparticles.Packaging/AndroidManifest.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/computeparticles/computeparticles.Packaging/assets/shaders/particle.comp.spv b/android/computeparticles/computeparticles.Packaging/assets/shaders/particle.comp.spv deleted file mode 100644 index cbeab76aab711165266b86e58660751b4343e6aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3664 zcmZvd+jCS!5XOh><_0kk0z?pE0xDDmBbRWAkxN2ki3SrvK)h|v?jFLzW;bR}R#enP zMDhCMAK{a)TE1B2|73Zo((?PwPRB!vNp($sU-xwPbkCk-TK2U&w?Tg$?pxPfJ+4LA zxlOLk@44yA)5GYMxHMTynm#+lm8*@(ay^kpo9q7{pIa`(B|#tSSxk{}NRi>(q zS}~%)sY!3oxD(p1Ow9PMqO*K0uemjjK9=Ho^161qj~_jSyY9Jm&oL#nH7OS>@wwV^ zRi(>2<3_bF7izvf@;3a0!DSFtxC-f1{ng@K$^ELdZ9$)0{`&A&qmoereAR6Tpak_?=g<1Gt)Q*kV zR^#Qs8RnZoKk&aEY}ALH{T&I*aLuEYU%~YDD+zDd@Ym9bcTlI932zP`1lw`lnVPsi zi+=qnrd2z4V%~fsnK_tu#WyL9`97rawRL!I9WDest}M*|sDwAdyEql@F%tOGs)+nx zvmKKT$9d-Zf`r*KZ+w{XDGB`D5bt#0e$*Lee^x>q_&=0mLObhk`JdH3E`g&L=>MpT z=x|oYwDe{j?S8he2s3|Xj*T5(<~<|9KBErOtJ%Q8vzSqfg!|H8?3U+}^mHEmt69r~ zjW=X@u1Lpjd9F%_Ls;oN%ndtzAb~Z z|6QFV_j?j*;_c&44(fPcJBvE-xg&w24s@&I1L^cSediB_;mzmnBYZv*2Y*ks(x;Cl zaGbSspGddZ^c##Hcd=U7>GhEI6$!s3o8PK*dNi)PUex}n#D2po((OI)8@?yOKBND% z;Lid_jI5+pLY)~2wcv}7<-RXHo%?fPYPZ~9NVnYhUY2l{Tn{AJE%%p!BgUh0Rk>CSQ-^d4N{@bL(8Qvc7_u*aa5XJ|7HE@qhhwqaRlbYd(!Hk}ez;y|McT4y- zJ}JTWAjH`to!*852nVZjQlTBk#kFi1|AJu YT}F+sLbIzoI`|t^VLx!c>f%!N2Xosy<^TWy diff --git a/android/computeparticles/computeparticles.Packaging/assets/shaders/particle.vert.spv b/android/computeparticles/computeparticles.Packaging/assets/shaders/particle.vert.spv deleted file mode 100644 index d1d41eed9076db474cd8920f1753b89cc7ca4ea0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1144 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/computeparticles/computeparticles.Packaging/computeparticles.Packaging.androidproj b/android/computeparticles/computeparticles.Packaging/computeparticles.Packaging.androidproj deleted file mode 100644 index 688ae811..00000000 --- a/android/computeparticles/computeparticles.Packaging/computeparticles.Packaging.androidproj +++ /dev/null @@ -1,135 +0,0 @@ - - - - - Debug - ARM - - - Release - ARM - - - Debug - ARM64 - - - Release - ARM64 - - - Debug - x64 - - - Release - x64 - - - Debug - x86 - - - Release - x86 - - - - computeparticles - 14.0 - 1.0 - 07b432c0-5242-4eb4-982b-ca895d032ae5 - - - - true - - - false - - - true - android-21 - - - false - - - true - - - false - - - true - - - false - - - - - - - $(SolutionDir)$(Platform)\$(Configuration)\$(RootNamespace) - - - - $(RootNamespace) - - - - - $(RootNamespace) - - - - - $(RootNamespace) - - - - - $(RootNamespace) - - - - - $(RootNamespace) - - - - - $(RootNamespace) - - - - - $(RootNamespace) - - - - - $(RootNamespace) - - - - - - - - - - - - - - - - - {622593a1-7b0d-4d71-8369-371601b8bee1} - - - - - \ No newline at end of file diff --git a/android/computeparticles/computeparticles.Packaging/project.properties b/android/computeparticles/computeparticles.Packaging/project.properties deleted file mode 100644 index 802a49f6..00000000 --- a/android/computeparticles/computeparticles.Packaging/project.properties +++ /dev/null @@ -1,3 +0,0 @@ -# Project target -target=$(androidapilevel) -# Provide path to the directory where prebuilt external jar files are by setting jar.libs.dir= diff --git a/android/computeparticles/computeparticles.Packaging/res/drawable/banner.png b/android/computeparticles/computeparticles.Packaging/res/drawable/banner.png deleted file mode 100644 index b9e715af33e4633c85bbc38f1726c26504d7620a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26204 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