diff --git a/android/raytracing/.gitignore b/android/raytracing/.gitignore new file mode 100644 index 00000000..7a5d249c --- /dev/null +++ b/android/raytracing/.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/raytracing/AndroidManifest.xml b/android/raytracing/AndroidManifest.xml new file mode 100644 index 00000000..beb3615a --- /dev/null +++ b/android/raytracing/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/android/raytracing/build.bat b/android/raytracing/build.bat new file mode 100644 index 00000000..be5e1960 --- /dev/null +++ b/android/raytracing/build.bat @@ -0,0 +1,17 @@ +cd jni +call ndk-build +if %ERRORLEVEL% EQU 0 ( + echo ndk-build has failed, build cancelled + cd.. + + mkdir "assets\shaders\raytracing" + xcopy "..\..\data\shaders\raytracing\*.spv" "assets\shaders\raytracing" /Y + + mkdir "res\drawable" + xcopy "..\..\android\images\icon.png" "res\drawable" /Y + + call ant debug -Dout.final.file=vulkanRaytracing.apk +) ELSE ( + echo error : ndk-build failed with errors! + cd.. +) diff --git a/android/raytracing/jni/Android.mk b/android/raytracing/jni/Android.mk new file mode 100644 index 00000000..327b28ea --- /dev/null +++ b/android/raytracing/jni/Android.mk @@ -0,0 +1,47 @@ +LOCAL_PATH := $(call my-dir)/../../raytracing + +# assimp + +include $(CLEAR_VARS) + +LOCAL_MODULE := assimp +LOCAL_SRC_FILES := $(LOCAL_PATH)/../../libs/assimp/$(TARGET_ARCH_ABI)/libassimp.a +include $(PREBUILT_STATIC_LIBRARY) + +# vulkan example + +DATADIR := $(LOCAL_PATH)/../../data + +include $(CLEAR_VARS) + +LOCAL_MODULE := vulkanRaytracing + +PROJECT_FILES := $(wildcard $(LOCAL_PATH)/../../raytracing/*.cpp) +PROJECT_FILES += $(wildcard $(LOCAL_PATH)/../../base/*.cpp) + +LOCAL_CPPFLAGS := -std=c++11 +LOCAL_CPPFLAGS += -D__STDC_LIMIT_MACROS +LOCAL_CPPFLAGS += -DVK_NO_PROTOTYPES +LOCAL_CPPFLAGS += -DVK_USE_PLATFORM_ANDROID_KHR + +LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../external/ +LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../external/glm +LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../external/gli +LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../external/assimp +LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../base/ +#LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../base/android + +LOCAL_SRC_FILES := $(PROJECT_FILES) + +LOCAL_LDLIBS := -landroid -llog -lz + +LOCAL_DISABLE_FORMAT_STRING_CHECKS := true + +LOCAL_STATIC_LIBRARIES += android_native_app_glue +LOCAL_STATIC_LIBRARIES += cpufeatures +LOCAL_STATIC_LIBRARIES += libassimp + +include $(BUILD_SHARED_LIBRARY) + +$(call import-module, android/native_app_glue) +$(call import-module, android/cpufeatures) diff --git a/android/raytracing/jni/Application.mk b/android/raytracing/jni/Application.mk new file mode 100644 index 00000000..62020feb --- /dev/null +++ b/android/raytracing/jni/Application.mk @@ -0,0 +1,5 @@ +APP_PLATFORM := android-19 +APP_ABI := armeabi-v7a +APP_STL := c++_static +APP_CPPFLAGS := -std=c++11 +NDK_TOOLCHAIN_VERSION := clang diff --git a/data/shaders/raytracing/generate-spirv.bat b/data/shaders/raytracing/generate-spirv.bat new file mode 100644 index 00000000..5266a7f9 --- /dev/null +++ b/data/shaders/raytracing/generate-spirv.bat @@ -0,0 +1,3 @@ +glslangvalidator -V texture.frag -o texture.frag.spv +glslangvalidator -V texture.vert -o texture.vert.spv +glslangvalidator -V raytracing.comp -o raytracing.comp.spv \ No newline at end of file diff --git a/data/shaders/raytracing/raytracing.comp b/data/shaders/raytracing/raytracing.comp new file mode 100644 index 00000000..f35e61a6 --- /dev/null +++ b/data/shaders/raytracing/raytracing.comp @@ -0,0 +1,187 @@ +// Shader is looseley based on the ray tracing coding session by Inigo Quilez (www.iquilezles.org) + +#version 450 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (local_size_x =16, local_size_y = 16) in; +layout (binding = 0, rgba8) uniform image2D resultImage; + +#define MAXLEN 1000.0 +#define PLANEID 1 +#define SPHERECOUNT 3 + +struct Camera { + vec3 pos; + vec3 lookat; + float fov; +}; + +layout (binding = 1) uniform UBO +{ + vec3 lightPos; + float aspectRatio; + vec4 fogColor; + Camera camera; + mat4 rotMat; +} ubo; + +// Lighting calculations + +float lightDiffuse(vec3 normal, vec3 lightDir) +{ + return clamp(dot(normal, lightDir), 0.0, 1.0); +} + +float lightSpecular(vec3 normal, vec3 lightDir) +{ + vec3 viewVec = normalize(ubo.camera.pos); + vec3 halfVec = normalize(lightDir + viewVec); + return pow(clamp(dot(normal, halfVec), 0.0, 1.0), 16.0); +} + +// Primitives + +// Basic material description +struct Material +{ + vec3 diffuse; + vec3 specular; +}; + +// Sphere +struct Sphere +{ + int id; + vec3 pos; + float r; + Material material; +} sphere; + +Sphere spheres[SPHERECOUNT]; + +float sphereIntersect(in vec3 rayO, in vec3 rayD, in Sphere sphere) +{ + vec3 oc = rayO - sphere.pos; + float b = 2.0 * dot(oc, rayD); + float c = dot(oc, oc) - sphere.r*sphere.r; + float h = b*b - 4.0*c; + if (h < 0.0) + { + return -1.0; + } + float t = (-b - sqrt(h)) / 2.0; + return t; +} + +vec3 sphereNormal(in vec3 pos, in Sphere sphere) +{ + return (pos - sphere.pos) / sphere.r; +} + +// Plane + +float planeIntersect(vec3 rayO, vec3 rayD) +{ + return -rayO.y/rayD.y; +} + +vec3 planeNormal(in vec3 pos) +{ + return vec3(0.0, 1.0, 0.0); +} + +int intersect(in vec3 rayO, in vec3 rayD, out float resT) +{ + int id = -1; + resT = MAXLEN; + + float tplane = planeIntersect(rayO, rayD); + if ((tplane > 0.0) && (tplane < resT)) + { + id = PLANEID; + resT = tplane; + } + + for (int i = 0; i < SPHERECOUNT; i++) + { + float tSphere = sphereIntersect(rayO, rayD, spheres[i]); + if (tSphere > 0.0) + { + id = spheres[i].id; + resT = tSphere; + break; + } + } + + return id; +} + +vec3 fog(in float t, in vec3 color) +{ + return mix(color, ubo.fogColor.rgb, clamp(sqrt(t*t)/10.0, 0.0, 1.0)); +} + +void main() +{ + // Scene setup + spheres[0].id = 2; + spheres[0].pos = vec3(-2.25, 1.0, 0.0); + spheres[0].r = 1.0; + spheres[0].material.diffuse = vec3(1.0, 0.0, 0.0); + spheres[0].material.specular = vec3(1.0, 1.0, 1.0); + + spheres[1].id = 3; + spheres[1].pos = vec3(2.25, 1.0, 0.0); + spheres[1].r = 1.0; + spheres[1].material.diffuse = vec3(0.0, 1.0, 0.0); + spheres[1].material.specular = vec3(1.0, 1.0, 1.0); + + spheres[2].id = 4; + spheres[2].pos = vec3(0.0, 1.0, 0.0); + spheres[2].r = 1.0; + spheres[2].material.diffuse = vec3(0.0, 0.0, 1.0); + spheres[2].material.specular = vec3(1.0, 1.0, 1.0); + + ivec2 pixelCoord = ivec2(gl_GlobalInvocationID.xy); + ivec2 dim = imageSize(resultImage); + vec2 uv = vec2(gl_GlobalInvocationID.xy)/dim; + + vec3 rayO = ubo.camera.pos; + vec3 rayD = normalize(vec3((-1.0 + 2.0 * uv) * vec2(ubo.aspectRatio, 1.0), -1.0)); + + // Get intersected object ID + float t; + int objectID = intersect(rayO, rayD, t); + + vec3 color = vec3(0.0); + + if (objectID == PLANEID) + { + vec3 pos = rayO + t * rayD; + vec3 normal = planeNormal(pos); + vec3 lightVec = normalize(ubo.lightPos - pos); + float diffuse = clamp(dot(normal, lightVec), 0.0, 1.0); + color = vec3(1.0, 1.0, 1.0) * diffuse; + } + else + { + for (int i = 0; i < SPHERECOUNT; i++) + { + if (objectID == spheres[i].id) + { + vec3 pos = rayO + t * rayD; + vec3 lightVec = normalize(ubo.lightPos - pos); + vec3 normal = sphereNormal(pos, spheres[i]); + float diffuse = lightDiffuse(normal, lightVec); + float specular = lightSpecular(normal, lightVec); + color = diffuse * spheres[i].material.diffuse + specular * spheres[i].material.specular; + } + } + } + + color = fog(t, color); + + imageStore(resultImage, ivec2(gl_GlobalInvocationID.xy), vec4(color, 0.0)); +} \ No newline at end of file diff --git a/data/shaders/raytracing/raytracing.comp.spv b/data/shaders/raytracing/raytracing.comp.spv new file mode 100644 index 00000000..88cd2719 Binary files /dev/null and b/data/shaders/raytracing/raytracing.comp.spv differ diff --git a/data/shaders/raytracing/texture.frag b/data/shaders/raytracing/texture.frag new file mode 100644 index 00000000..389026a7 --- /dev/null +++ b/data/shaders/raytracing/texture.frag @@ -0,0 +1,15 @@ +#version 450 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (binding = 0) uniform sampler2D samplerColor; + +layout (location = 0) in vec2 inUV; + +layout (location = 0) out vec4 outFragColor; + +void main() +{ + outFragColor = texture(samplerColor, vec2(inUV.s, 1.0 - inUV.t)); +} \ No newline at end of file diff --git a/data/shaders/raytracing/texture.frag.spv b/data/shaders/raytracing/texture.frag.spv new file mode 100644 index 00000000..c8595438 Binary files /dev/null and b/data/shaders/raytracing/texture.frag.spv differ diff --git a/data/shaders/raytracing/texture.vert b/data/shaders/raytracing/texture.vert new file mode 100644 index 00000000..2b8a0e9e --- /dev/null +++ b/data/shaders/raytracing/texture.vert @@ -0,0 +1,15 @@ +#version 450 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (location = 0) in vec3 inPos; +layout (location = 1) in vec2 inUV; + +layout (location = 0) out vec2 outUV; + +void main() +{ + outUV = inUV; + gl_Position = vec4(inPos.xyz, 1.0); +} diff --git a/data/shaders/raytracing/texture.vert.spv b/data/shaders/raytracing/texture.vert.spv new file mode 100644 index 00000000..0350aaa0 Binary files /dev/null and b/data/shaders/raytracing/texture.vert.spv differ diff --git a/raytracing/raytracing.cpp b/raytracing/raytracing.cpp new file mode 100644 index 00000000..067e1041 --- /dev/null +++ b/raytracing/raytracing.cpp @@ -0,0 +1,794 @@ +/* +* Vulkan Example - Compute shader ray tracing +* +* 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 +#include +#include +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include + +#include +#include "vulkanexamplebase.h" + +#define VERTEX_BUFFER_BIND_ID 0 +#define ENABLE_VALIDATION false + +#define TEX_DIM 2048 + +// Vertex layout for this example +struct Vertex { + float pos[3]; + float uv[2]; +}; + +class VulkanExample : public VulkanExampleBase +{ +private: + vkTools::VulkanTexture textureComputeTarget; +public: + struct { + VkPipelineVertexInputStateCreateInfo inputState; + std::vector bindingDescriptions; + std::vector attributeDescriptions; + } vertices; + + struct { + vkMeshLoader::MeshBuffer quad; + } meshes; + + vkTools::UniformData uniformDataCompute; + + struct { + glm::vec3 lightPos; + // Aspect ratio of the viewport + float aspectRatio; + glm::vec4 fogColor = glm::vec4(0.025f, 0.025f, 0.025f, 0.0f); + struct { + glm::vec3 pos = glm::vec3(0.0f, 1.0f, 4.0f); + glm::vec3 lookat = glm::vec3(0.0f, 0.5f, 0.0f); + float fov = 10.0f; + } camera; + } uboCompute; + + struct { + VkPipeline display; + VkPipeline compute; + } pipelines; + + int vertexBufferSize; + + VkQueue computeQueue; + VkCommandBuffer computeCmdBuffer; + VkPipelineLayout computePipelineLayout; + VkDescriptorSet computeDescriptorSet; + VkDescriptorSetLayout computeDescriptorSetLayout; + VkDescriptorPool computeDescriptorPool; + + VkPipelineLayout pipelineLayout; + VkDescriptorSet descriptorSetPostCompute; + VkDescriptorSetLayout descriptorSetLayout; + + VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) + { + zoom = -2.0f; + title = "Vulkan Example - Compute shader ray tracing"; + uboCompute.aspectRatio = (float)width / (float)height; + paused = true; + } + + ~VulkanExample() + { + // Clean up used Vulkan resources + // Note : Inherited destructor cleans up resources stored in base class + + vkDestroyPipeline(device, pipelines.display, nullptr); + vkDestroyPipeline(device, pipelines.compute, nullptr); + + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + + vkMeshLoader::freeMeshBufferResources(device, &meshes.quad); + + vkTools::destroyUniformData(device, &uniformDataCompute); + + vkFreeCommandBuffers(device, cmdPool, 1, &computeCmdBuffer); + + textureLoader->destroyTexture(textureComputeTarget); + } + + // Prepare a texture target that is used to store compute shader calculations + void prepareTextureTarget(vkTools::VulkanTexture *tex, uint32_t width, uint32_t height, VkFormat format) + { + // Get device properties for the requested texture format + VkFormatProperties formatProperties; + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties); + // Check if requested image format supports image storage operations + assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT); + + // Prepare blit target texture + tex->width = width; + tex->height = height; + + VkImageCreateInfo imageCreateInfo = vkTools::initializers::imageCreateInfo(); + imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; + imageCreateInfo.format = format; + imageCreateInfo.extent = { width, height, 1 }; + imageCreateInfo.mipLevels = 1; + imageCreateInfo.arrayLayers = 1; + imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; + imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED; + // Image will be sampled in the fragment shader and used as storage target in the compute shader + imageCreateInfo.usage = + VK_IMAGE_USAGE_SAMPLED_BIT | + VK_IMAGE_USAGE_STORAGE_BIT; + imageCreateInfo.flags = 0; + + VkMemoryAllocateInfo memAllocInfo = vkTools::initializers::memoryAllocateInfo(); + VkMemoryRequirements memReqs; + + vkTools::checkResult(vkCreateImage(device, &imageCreateInfo, nullptr, &tex->image)); + vkGetImageMemoryRequirements(device, tex->image, &memReqs); + memAllocInfo.allocationSize = memReqs.size; + getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memAllocInfo.memoryTypeIndex); + vkTools::checkResult(vkAllocateMemory(device, &memAllocInfo, nullptr, &tex->deviceMemory)); + vkTools::checkResult(vkBindImageMemory(device, tex->image, tex->deviceMemory, 0)); + + tex->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + vkTools::setImageLayout( + setupCmdBuffer, tex->image, + VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_PREINITIALIZED, + tex->imageLayout); + + // Create sampler + 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_REPEAT; + sampler.addressModeV = sampler.addressModeU; + sampler.addressModeW = sampler.addressModeU; + sampler.mipLodBias = 0.0f; + sampler.maxAnisotropy = 0; + sampler.compareOp = VK_COMPARE_OP_NEVER; + sampler.minLod = 0.0f; + sampler.maxLod = 0.0f; + sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + vkTools::checkResult(vkCreateSampler(device, &sampler, nullptr, &tex->sampler)); + + // Create image view + VkImageViewCreateInfo view = vkTools::initializers::imageViewCreateInfo(); + 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 = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + view.image = tex->image; + vkTools::checkResult(vkCreateImageView(device, &view, nullptr, &tex->view)); + } + + void buildCommandBuffers() + { + // Destroy command buffers if already present + if (!checkCommandBuffers()) + { + destroyCommandBuffers(); + createCommandBuffers(); + } + + VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo(); + + VkClearValue clearValues[2]; + clearValues[0].color = defaultClearColor; + clearValues[0].color = { {0.0f, 0.0f, 0.2f, 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); + + // Image memory barrier to make sure that compute + // shader writes are finished before sampling + // from the texture + VkImageMemoryBarrier imageMemoryBarrier = {}; + imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + imageMemoryBarrier.pNext = NULL; + imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; + imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + imageMemoryBarrier.image = textureComputeTarget.image; + imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; + imageMemoryBarrier.dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT; + vkCmdPipelineBarrier( + drawCmdBuffers[i], + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_FLAGS_NONE, + 0, nullptr, + 0, nullptr, + 1, &imageMemoryBarrier); + + 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); + + VkDeviceSize offsets[1] = { 0 }; + vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &meshes.quad.vertices.buf, offsets); + vkCmdBindIndexBuffer(drawCmdBuffers[i], meshes.quad.indices.buf, 0, VK_INDEX_TYPE_UINT32); + + // Display ray traced image generated by compute shader as a full screen quad + + vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSetPostCompute, 0, NULL); + vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.display); + + vkCmdDrawIndexed(drawCmdBuffers[i], meshes.quad.indexCount, 1, 0, 0, 0); + + vkCmdEndRenderPass(drawCmdBuffers[i]); + + err = vkEndCommandBuffer(drawCmdBuffers[i]); + assert(!err); + } + + } + + void buildComputeCommandBuffer() + { + VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo(); + + VkResult err = vkBeginCommandBuffer(computeCmdBuffer, &cmdBufInfo); + assert(!err); + + vkCmdBindPipeline(computeCmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipelines.compute); + vkCmdBindDescriptorSets(computeCmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, computePipelineLayout, 0, 1, &computeDescriptorSet, 0, 0); + + vkCmdDispatch(computeCmdBuffer, textureComputeTarget.width / 16, textureComputeTarget.height / 16, 1); + + vkEndCommandBuffer(computeCmdBuffer); + } + + 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); + + // Command buffer to be sumitted to the queue + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; + + // Submit to queue + err = vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); + assert(!err); + + submitPrePresentBarrier(swapChain.buffers[currentBuffer].image); + + err = swapChain.queuePresent(queue, currentBuffer, semaphores.renderComplete); + assert(!err); + + err = vkQueueWaitIdle(queue); + 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); + } + + // Setup vertices for a single uv-mapped quad + void generateQuad() + { +#define dim 1.0f + std::vector vertexBuffer = + { + { { dim, dim, 0.0f }, { 1.0f, 1.0f } }, + { { -dim, dim, 0.0f }, { 0.0f, 1.0f } }, + { { -dim, -dim, 0.0f }, { 0.0f, 0.0f } }, + { { dim, -dim, 0.0f }, { 1.0f, 0.0f } } + }; +#undef dim + + createBuffer( + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + vertexBuffer.size() * sizeof(Vertex), + vertexBuffer.data(), + &meshes.quad.vertices.buf, + &meshes.quad.vertices.mem); + + // Setup indices + std::vector indexBuffer = { 0,1,2, 2,3,0 }; + meshes.quad.indexCount = indexBuffer.size(); + + createBuffer( + VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + indexBuffer.size() * sizeof(uint32_t), + indexBuffer.data(), + &meshes.quad.indices.buf, + &meshes.quad.indices.mem); + } + + void setupVertexDescriptions() + { + // Binding description + vertices.bindingDescriptions.resize(1); + vertices.bindingDescriptions[0] = + vkTools::initializers::vertexInputBindingDescription( + VERTEX_BUFFER_BIND_ID, + sizeof(Vertex), + 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_R32G32B32_SFLOAT, + 0); + // Location 1 : Texture coordinates + vertices.attributeDescriptions[1] = + vkTools::initializers::vertexInputAttributeDescription( + VERTEX_BUFFER_BIND_ID, + 1, + VK_FORMAT_R32G32_SFLOAT, + sizeof(float) * 3); + + // 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 setupDescriptorPool() + { + std::vector poolSizes = + { + vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2), + // Graphics pipeline uses image samplers for display + vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4), + // Compute pipeline uses storage images image loads and stores + vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1), + }; + + VkDescriptorPoolCreateInfo descriptorPoolInfo = + vkTools::initializers::descriptorPoolCreateInfo( + poolSizes.size(), + poolSizes.data(), + 3); + + VkResult vkRes = vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool); + assert(!vkRes); + } + + void setupDescriptorSetLayout() + { + std::vector setLayoutBindings = + { + // 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, &descriptorSetPostCompute); + assert(!vkRes); + + // Image descriptor for the color map texture + VkDescriptorImageInfo texDescriptor = + vkTools::initializers::descriptorImageInfo( + textureComputeTarget.sampler, + textureComputeTarget.view, + VK_IMAGE_LAYOUT_GENERAL); + + std::vector writeDescriptorSets = + { + // Binding 0 : Fragment shader texture sampler + vkTools::initializers::writeDescriptorSet( + descriptorSetPostCompute, + VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + 0, + &texDescriptor) + }; + + vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL); + } + + // Create a separate command buffer for compute commands + void createComputeCommandBuffer() + { + VkCommandBufferAllocateInfo cmdBufAllocateInfo = + vkTools::initializers::commandBufferAllocateInfo( + cmdPool, + VK_COMMAND_BUFFER_LEVEL_PRIMARY, + 1); + + VkResult vkRes = vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &computeCmdBuffer); + assert(!vkRes); + } + + void preparePipelines() + { + VkResult err; + + VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = + vkTools::initializers::pipelineInputAssemblyStateCreateInfo( + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_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 = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState = + vkTools::initializers::pipelineDynamicStateCreateInfo( + dynamicStateEnables.data(), + dynamicStateEnables.size(), + 0); + + // Display pipeline + std::array shaderStages; + + shaderStages[0] = loadShader(getAssetPath() + "shaders/raytracing/texture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shaderStages[1] = loadShader(getAssetPath() + "shaders/raytracing/texture.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; + + err = vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.display); + assert(!err); + } + + // Prepare the compute pipeline that generates the ray traced image + void prepareCompute() + { + std::vector setLayoutBindings = { + // Binding 0 : Sampled image (write) + vkTools::initializers::descriptorSetLayoutBinding( + VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + VK_SHADER_STAGE_COMPUTE_BIT, + 0), + // Binding 1 : Uniform buffer block + 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 computeTexDescriptors = + { + vkTools::initializers::descriptorImageInfo( + VK_NULL_HANDLE, + textureComputeTarget.view, + VK_IMAGE_LAYOUT_GENERAL) + }; + + std::vector computeWriteDescriptorSets = + { + // Binding 0 : Output storage image + vkTools::initializers::writeDescriptorSet( + computeDescriptorSet, + VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + 0, + &computeTexDescriptors[0]), + // Binding 1 : Uniform buffer block + vkTools::initializers::writeDescriptorSet( + computeDescriptorSet, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + 1, + &uniformDataCompute.descriptor) + }; + + vkUpdateDescriptorSets(device, computeWriteDescriptorSets.size(), computeWriteDescriptorSets.data(), 0, NULL); + + + // Create compute shader pipelines + VkComputePipelineCreateInfo computePipelineCreateInfo = + vkTools::initializers::computePipelineCreateInfo( + computePipelineLayout, + 0); + + computePipelineCreateInfo.stage = loadShader(getAssetPath() + "shaders/raytracing/raytracing.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT); + vkTools::checkResult(vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &pipelines.compute)); + } + + // Prepare and initialize uniform buffer containing shader uniforms + void prepareUniformBuffers() + { + // Vertex shader uniform buffer block + createBuffer( + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + sizeof(uboCompute), + &uboCompute, + &uniformDataCompute.buffer, + &uniformDataCompute.memory, + &uniformDataCompute.descriptor); + + updateUniformBuffers(); + } + + void updateUniformBuffers() + { + uboCompute.lightPos.x = 0.0f; + uboCompute.lightPos.y = 1.0f; + uboCompute.lightPos.z = 1.5f; + uint8_t *pData; + vkTools::checkResult(vkMapMemory(device, uniformDataCompute.memory, 0, sizeof(uboCompute), 0, (void **)&pData)); + memcpy(pData, &uboCompute, sizeof(uboCompute)); + vkUnmapMemory(device, uniformDataCompute.memory); + } + + // 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 prepare() + { + VulkanExampleBase::prepare(); + generateQuad(); + getComputeQueue(); + createComputeCommandBuffer(); + setupVertexDescriptions(); + prepareUniformBuffers(); + prepareTextureTarget( + &textureComputeTarget, + TEX_DIM, + TEX_DIM, + VK_FORMAT_R8G8B8A8_UNORM); + setupDescriptorSetLayout(); + preparePipelines(); + setupDescriptorPool(); + setupDescriptorSet(); + prepareCompute(); + buildCommandBuffers(); + buildComputeCommandBuffer(); + prepared = true; + } + + virtual void render() + { + if (!prepared) + return; + vkDeviceWaitIdle(device); + draw(); + vkDeviceWaitIdle(device); + if (!paused) + { + updateUniformBuffers(); + } + } + + virtual void viewChanged() + { + updateUniformBuffers(); + } +}; + +VulkanExample *vulkanExample; + +#if defined(_WIN32) +LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (vulkanExample != NULL) + { + vulkanExample->handleMessages(hWnd, uMsg, wParam, lParam); + } + return (DefWindowProc(hWnd, uMsg, wParam, lParam)); +} +#elif defined(__linux__) && !defined(__ANDROID__) +static void handleEvent(const xcb_generic_event_t *event) +{ + if (vulkanExample != NULL) + { + vulkanExample->handleEvent(event); + } +} +#endif + +// Main entry point +#if defined(_WIN32) +// Windows entry point +int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, int nCmdShow) +#elif defined(__ANDROID__) +// Android entry point +void android_main(android_app* state) +#elif defined(__linux__) +// Linux entry point +int main(const int argc, const char *argv[]) +#endif +{ +#if defined(__ANDROID__) + // Removing this may cause the compiler to omit the main entry point + // which would make the application crash at start + app_dummy(); +#endif + vulkanExample = new VulkanExample(); +#if defined(_WIN32) + vulkanExample->setupWindow(hInstance, WndProc); +#elif defined(__ANDROID__) + // Attach vulkan example to global android application state + state->userData = vulkanExample; + state->onAppCmd = VulkanExample::handleAppCommand; + state->onInputEvent = VulkanExample::handleAppInput; + vulkanExample->androidApp = state; +#elif defined(__linux__) + vulkanExample->setupWindow(); +#endif +#if !defined(__ANDROID__) + vulkanExample->initSwapchain(); + vulkanExample->prepare(); +#endif + vulkanExample->renderLoop(); + delete(vulkanExample); +#if !defined(__ANDROID__) + return 0; +#endif +} diff --git a/raytracing/raytracing.vcxproj b/raytracing/raytracing.vcxproj new file mode 100644 index 00000000..5bfc8967 --- /dev/null +++ b/raytracing/raytracing.vcxproj @@ -0,0 +1,96 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {8B1C24E5-CC00-484C-9F6F-8FFCBDA3AA30} + Win32Proj + 8.1 + + + + Application + true + v140 + + + Application + false + v140 + + + + + + + + + + + + + true + $(SolutionDir)\bin\ + $(SolutionDir)\bin\intermediate\$(ProjectName)\$(ConfigurationName) + + + true + $(SolutionDir)\bin\ + $(SolutionDir)\bin\intermediate\$(ProjectName)\$(ConfigurationName) + + + + WIN32;_DEBUG;_WINDOWS;VK_USE_PLATFORM_WIN32_KHR;_USE_MATH_DEFINES;NOMINMAX;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + ProgramDatabase + Disabled + ..\base;..\external\glm;..\external\gli;..\external\assimp;..\external;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + true + Windows + ..\libs\vulkan\vulkan-1.lib;..\libs\assimp\assimp.lib;%(AdditionalDependencies) + + + + + WIN32;NDEBUG;_WINDOWS;VK_USE_PLATFORM_WIN32_KHR;_USE_MATH_DEFINES;NOMINMAX;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + ..\base;..\external\glm;..\external\gli;..\external\assimp;..\external;%(AdditionalIncludeDirectories) + MaxSpeed + + + true + Windows + true + true + ..\libs\vulkan\vulkan-1.lib;..\libs\assimp\assimp.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/raytracing/raytracing.vcxproj.filters b/raytracing/raytracing.vcxproj.filters new file mode 100644 index 00000000..8c9d76b7 --- /dev/null +++ b/raytracing/raytracing.vcxproj.filters @@ -0,0 +1,42 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/vulkanExamples.sln b/vulkanExamples.sln index 89a5801f..7518d8fc 100644 --- a/vulkanExamples.sln +++ b/vulkanExamples.sln @@ -59,6 +59,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shadowmapping", "shadowmapp EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "particlefire", "particlefire\particlefire.vcxproj", "{A8DDE46D-0C36-49E5-83CB-19FF69493FA0}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "raytracing", "raytracing\raytracing.vcxproj", "{8B1C24E5-CC00-484C-9F6F-8FFCBDA3AA30}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -177,6 +179,10 @@ Global {A8DDE46D-0C36-49E5-83CB-19FF69493FA0}.Debug|x64.Build.0 = Debug|x64 {A8DDE46D-0C36-49E5-83CB-19FF69493FA0}.Release|x64.ActiveCfg = Release|x64 {A8DDE46D-0C36-49E5-83CB-19FF69493FA0}.Release|x64.Build.0 = Release|x64 + {8B1C24E5-CC00-484C-9F6F-8FFCBDA3AA30}.Debug|x64.ActiveCfg = Debug|x64 + {8B1C24E5-CC00-484C-9F6F-8FFCBDA3AA30}.Debug|x64.Build.0 = Debug|x64 + {8B1C24E5-CC00-484C-9F6F-8FFCBDA3AA30}.Release|x64.ActiveCfg = Release|x64 + {8B1C24E5-CC00-484C-9F6F-8FFCBDA3AA30}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE